am b1f5a54a: Merge "VCard refactoring backport." into gingerbread

Merge commit 'b1f5a54a2abd95d03dc96f5f691495d2d7dfadc7' into gingerbread-plus-aosp

* commit 'b1f5a54a2abd95d03dc96f5f691495d2d7dfadc7':
  VCard refactoring backport.
This commit is contained in:
Daisuke Miyakawa
2010-09-02 12:59:36 -07:00
committed by Android Git Automerger
46 changed files with 4274 additions and 2739 deletions

View File

@@ -27,7 +27,6 @@ import java.util.Map;
new HashMap<Character, String>(); new HashMap<Character, String>();
static { static {
// There's no logical mapping rule in Unicode. Sigh.
sHalfWidthMap.put('\u3001', "\uFF64"); sHalfWidthMap.put('\u3001', "\uFF64");
sHalfWidthMap.put('\u3002', "\uFF61"); sHalfWidthMap.put('\u3002', "\uFF61");
sHalfWidthMap.put('\u300C', "\uFF62"); sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@ import java.util.Map;
} }
/** /**
* Return half-width version of that character if possible. Return null if not possible * Returns half-width version of that character if possible. Returns null if not possible
* @param ch input character * @param ch input character
* @return CharSequence object if the mapping for ch exists. Return null otherwise. * @return CharSequence object if the mapping for ch exists. Return null otherwise.
*/ */
public static String tryGetHalfWidthText(char ch) { public static String tryGetHalfWidthText(final char ch) {
if (sHalfWidthMap.containsKey(ch)) { if (sHalfWidthMap.containsKey(ch)) {
return sHalfWidthMap.get(ch); return sHalfWidthMap.get(ch);
} else { } else {

View File

@@ -30,11 +30,10 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64;
import android.util.CharsetUtils; import android.util.CharsetUtils;
import android.util.Log; import android.util.Log;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException; import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList; import java.util.ArrayList;
@@ -47,7 +46,23 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* The class which lets users create their own vCard String. * <p>
* The class which lets users create their own vCard String. Typical usage is as follows:
* </p>
* <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType);
* builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
* .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
* .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
* .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
* .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
* .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
* .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
* .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
* .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
* .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
* .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
* .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
* return builder.toString();</pre>
*/ */
public class VCardBuilder { public class VCardBuilder {
private static final String LOG_TAG = "VCardBuilder"; private static final String LOG_TAG = "VCardBuilder";
@@ -75,81 +90,129 @@ public class VCardBuilder {
private static final String VCARD_WS = " "; private static final String VCARD_WS = " ";
private static final String VCARD_PARAM_EQUAL = "="; private static final String VCARD_PARAM_EQUAL = "=";
private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE"; private static final String VCARD_PARAM_ENCODING_QP =
"ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64"; private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b"; "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64;
private static final String VCARD_PARAM_ENCODING_BASE64_AS_B =
"ENCODING=" + VCardConstants.PARAM_ENCODING_B;
private static final String SHIFT_JIS = "SHIFT_JIS"; private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8";
private final int mVCardType; private final int mVCardType;
private final boolean mIsV30; private final boolean mIsV30OrV40;
private final boolean mIsJapaneseMobilePhone; private final boolean mIsJapaneseMobilePhone;
private final boolean mOnlyOneNoteFieldIsAvailable; private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo; private final boolean mIsDoCoMo;
private final boolean mShouldUseQuotedPrintable; private final boolean mShouldUseQuotedPrintable;
private final boolean mUsesAndroidProperty; private final boolean mUsesAndroidProperty;
private final boolean mUsesDefactProperty; private final boolean mUsesDefactProperty;
private final boolean mUsesUtf8;
private final boolean mUsesShiftJis;
private final boolean mAppendTypeParamName; private final boolean mAppendTypeParamName;
private final boolean mRefrainsQPToNameProperties; private final boolean mRefrainsQPToNameProperties;
private final boolean mNeedsToConvertPhoneticString; private final boolean mNeedsToConvertPhoneticString;
private final boolean mShouldAppendCharsetParam; private final boolean mShouldAppendCharsetParam;
private final String mCharsetString; private final String mCharset;
private final String mVCardCharsetParameter; private final String mVCardCharsetParameter;
private StringBuilder mBuilder; private StringBuilder mBuilder;
private boolean mEndAppended; private boolean mEndAppended;
public VCardBuilder(final int vcardType) { public VCardBuilder(final int vcardType) {
// Default charset should be used
this(vcardType, null);
}
/**
* @param vcardType
* @param charset If null, we use default charset for export.
* @hide
*/
public VCardBuilder(final int vcardType, String charset) {
mVCardType = vcardType; mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType); Log.w(LOG_TAG,
"Should not use vCard 4.0 when building vCard. " +
"It is not officially published yet.");
mIsV30OrV40 = VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType);
mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType); mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType); mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType); mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType); mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType); mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType); mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType); mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType);
mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType); mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType);
mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8); // vCard 2.1 requires charset.
// vCard 3.0 does not allow it but we found some devices use it to determine
// the exact charset.
// We currently append it only when charset other than UTF_8 is used.
mShouldAppendCharsetParam =
!(VCardConfig.isVersion30(vcardType) && "UTF-8".equalsIgnoreCase(charset));
if (mIsDoCoMo) { if (VCardConfig.isDoCoMo(vcardType)) {
String charset; if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
try { Log.w(LOG_TAG,
charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name(); "The charset \"" + charset + "\" is used while "
} catch (UnsupportedCharsetException e) { + SHIFT_JIS + " is needed to be used.");
Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); if (TextUtils.isEmpty(charset)) {
charset = SHIFT_JIS; mCharset = SHIFT_JIS;
} else {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
}
} else {
if (mIsDoCoMo) {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"DoCoMo-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
} else {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"Career-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
}
mCharset = charset;
} }
mCharsetString = charset;
// Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but
// may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in
// Android, not shown to the public).
mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else if (mUsesShiftJis) {
String charset;
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
mCharsetString = charset;
mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS; mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS;
} else { } else {
mCharsetString = UTF_8; if (TextUtils.isEmpty(charset)) {
mVCardCharsetParameter = "CHARSET=" + UTF_8; Log.i(LOG_TAG,
"Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET
+ "\" for export.");
mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET;
mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET;
} else {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
mVCardCharsetParameter = "CHARSET=" + charset;
}
} }
clear(); clear();
} }
@@ -158,9 +221,14 @@ public class VCardBuilder {
mBuilder = new StringBuilder(); mBuilder = new StringBuilder();
mEndAppended = false; mEndAppended = false;
appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD); appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD);
if (mIsV30) { if (VCardConfig.isVersion40(mVCardType)) {
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V40);
} else if (VCardConfig.isVersion30(mVCardType)) {
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30); appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30);
} else { } else {
if (!VCardConfig.isVersion21(mVCardType)) {
Log.w(LOG_TAG, "Unknown vCard version detected.");
}
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21); appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
} }
} }
@@ -226,19 +294,128 @@ public class VCardBuilder {
return primaryContentValues; return primaryContentValues;
} }
/**
* To avoid unnecessary complication in logic, we use this method to construct N, FN
* properties for vCard 4.0.
*/
private VCardBuilder appendNamePropertiesV40(final List<ContentValues> contentValuesList) {
if (mIsDoCoMo || mNeedsToConvertPhoneticString) {
// Ignore all flags that look stale from the view of vCard 4.0 to
// simplify construction algorithm. Actually we don't have any vCard file
// available from real world yet, so we may need to re-enable some of these
// in the future.
Log.w(LOG_TAG, "Invalid flag is used in vCard 4.0 construction. Ignored.");
}
if (contentValuesList == null || contentValuesList.isEmpty()) {
appendLine(VCardConstants.PROPERTY_FN, "");
return this;
}
// We have difficulty here. How can we appropriately handle StructuredName with
// missing parts necessary for displaying while it has suppremental information.
//
// e.g. How to handle non-empty phonetic names with empty structured names?
final ContentValues contentValues = getPrimaryContentValue(contentValuesList);
String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
final String prefix = contentValues.getAsString(StructuredName.PREFIX);
final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
final String formattedName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
if (TextUtils.isEmpty(familyName)
&& TextUtils.isEmpty(givenName)
&& TextUtils.isEmpty(middleName)
&& TextUtils.isEmpty(prefix)
&& TextUtils.isEmpty(suffix)) {
if (TextUtils.isEmpty(formattedName)) {
appendLine(VCardConstants.PROPERTY_FN, "");
return this;
}
familyName = formattedName;
}
final String phoneticFamilyName =
contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME);
final String phoneticMiddleName =
contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME);
final String phoneticGivenName =
contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME);
final String escapedFamily = escapeCharacters(familyName);
final String escapedGiven = escapeCharacters(givenName);
final String escapedMiddle = escapeCharacters(middleName);
final String escapedPrefix = escapeCharacters(prefix);
final String escapedSuffix = escapeCharacters(suffix);
mBuilder.append(VCardConstants.PROPERTY_N);
if (!(TextUtils.isEmpty(phoneticFamilyName) &&
TextUtils.isEmpty(phoneticMiddleName) &&
TextUtils.isEmpty(phoneticGivenName))) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
final String sortAs = escapeCharacters(phoneticFamilyName)
+ ';' + escapeCharacters(phoneticGivenName)
+ ';' + escapeCharacters(phoneticMiddleName);
mBuilder.append("SORT-AS=").append(
VCardUtils.toStringAsV40ParamValue(sortAs));
}
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(escapedFamily);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(escapedGiven);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(escapedMiddle);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(escapedPrefix);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(escapedSuffix);
mBuilder.append(VCARD_END_OF_LINE);
if (TextUtils.isEmpty(formattedName)) {
// Note:
// DISPLAY_NAME doesn't exist while some other elements do, which is usually
// weird in Android, as DISPLAY_NAME should (usually) be constructed
// from the others using locale information and its code points.
Log.w(LOG_TAG, "DISPLAY_NAME is empty.");
final String escaped = escapeCharacters(VCardUtils.constructNameFromElements(
VCardConfig.getNameOrderType(mVCardType),
familyName, middleName, givenName, prefix, suffix));
appendLine(VCardConstants.PROPERTY_FN, escaped);
} else {
final String escapedFormatted = escapeCharacters(formattedName);
mBuilder.append(VCardConstants.PROPERTY_FN);
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(escapedFormatted);
mBuilder.append(VCARD_END_OF_LINE);
}
// We may need X- properties for phonetic names.
appendPhoneticNameFields(contentValues);
return this;
}
/** /**
* For safety, we'll emit just one value around StructuredName, as external importers * For safety, we'll emit just one value around StructuredName, as external importers
* may get confused with multiple "N", "FN", etc. properties, though it is valid in * may get confused with multiple "N", "FN", etc. properties, though it is valid in
* vCard spec. * vCard spec.
*/ */
public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) { public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
if (VCardConfig.isVersion40(mVCardType)) {
return appendNamePropertiesV40(contentValuesList);
}
if (contentValuesList == null || contentValuesList.isEmpty()) { if (contentValuesList == null || contentValuesList.isEmpty()) {
if (mIsDoCoMo) { if (VCardConfig.isVersion30(mVCardType)) {
appendLine(VCardConstants.PROPERTY_N, "");
} else if (mIsV30) {
// vCard 3.0 requires "N" and "FN" properties. // vCard 3.0 requires "N" and "FN" properties.
// vCard 4.0 does NOT require N, but we take care of possible backward
// compatibility issues.
appendLine(VCardConstants.PROPERTY_N, ""); appendLine(VCardConstants.PROPERTY_N, "");
appendLine(VCardConstants.PROPERTY_FN, ""); appendLine(VCardConstants.PROPERTY_FN, "");
} else if (mIsDoCoMo) {
appendLine(VCardConstants.PROPERTY_N, "");
} }
return this; return this;
} }
@@ -360,6 +537,7 @@ public class VCardBuilder {
encodeQuotedPrintable(displayName) : encodeQuotedPrintable(displayName) :
escapeCharacters(displayName); escapeCharacters(displayName);
// N
mBuilder.append(VCardConstants.PROPERTY_N); mBuilder.append(VCardConstants.PROPERTY_N);
if (shouldAppendCharsetParam(displayName)) { if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_SEPARATOR);
@@ -376,11 +554,13 @@ public class VCardBuilder {
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
// FN
mBuilder.append(VCardConstants.PROPERTY_FN); mBuilder.append(VCardConstants.PROPERTY_FN);
// Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it
// when it would be useful for external importers, assuming no external // when it would be useful or necessary for external importers,
// importer allows this vioration. // assuming the external importer allows this vioration of the spec.
if (shouldAppendCharsetParam(displayName)) { if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter); mBuilder.append(mVCardCharsetParameter);
@@ -388,8 +568,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedDisplayName); mBuilder.append(encodedDisplayName);
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsV30) { } else if (VCardConfig.isVersion30(mVCardType)) {
// vCard 3.0 specification requires these fields.
appendLine(VCardConstants.PROPERTY_N, ""); appendLine(VCardConstants.PROPERTY_N, "");
appendLine(VCardConstants.PROPERTY_FN, ""); appendLine(VCardConstants.PROPERTY_FN, "");
} else if (mIsDoCoMo) { } else if (mIsDoCoMo) {
@@ -400,6 +579,9 @@ public class VCardBuilder {
return this; return this;
} }
/**
* Emits SOUND;IRMC, SORT-STRING, and de-fact values for phonetic names like X-PHONETIC-FAMILY.
*/
private void appendPhoneticNameFields(final ContentValues contentValues) { private void appendPhoneticNameFields(final ContentValues contentValues) {
final String phoneticFamilyName; final String phoneticFamilyName;
final String phoneticMiddleName; final String phoneticMiddleName;
@@ -439,13 +621,18 @@ public class VCardBuilder {
return; return;
} }
// Try to emit the field(s) related to phonetic name. if (VCardConfig.isVersion40(mVCardType)) {
if (mIsV30) { // We don't want SORT-STRING anyway.
final String sortString = VCardUtils } else if (VCardConfig.isVersion30(mVCardType)) {
.constructNameFromElements(mVCardType, final String sortString =
VCardUtils.constructNameFromElements(mVCardType,
phoneticFamilyName, phoneticMiddleName, phoneticGivenName); phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
mBuilder.append(VCardConstants.PROPERTY_SORT_STRING); mBuilder.append(VCardConstants.PROPERTY_SORT_STRING);
if (shouldAppendCharsetParam(sortString)) { if (VCardConfig.isVersion30(mVCardType) && shouldAppendCharsetParam(sortString)) {
// vCard 3.0 does not force us to use UTF-8 and actually we see some
// programs which emit this value. It is incorrect from the view of
// specification, but actually necessary for parsing vCard with non-UTF-8
// charsets, expecting other parsers not get confused with this value.
mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter); mBuilder.append(mVCardCharsetParameter);
} }
@@ -454,18 +641,18 @@ public class VCardBuilder {
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsJapaneseMobilePhone) { } else if (mIsJapaneseMobilePhone) {
// Note: There is no appropriate property for expressing // Note: There is no appropriate property for expressing
// phonetic name in vCard 2.1, while there is in // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in
// vCard 3.0 (SORT-STRING). // vCard 3.0 (SORT-STRING).
// We chose to use DoCoMo's way when the device is Japanese one // We use DoCoMo's way when the device is Japanese one since it is already
// since it is supported by // supported by a lot of Japanese mobile phones.
// a lot of Japanese mobile phones. This is "X-" property, so // This is "X-" property, so any parser hopefully would not get
// any parser hopefully would not get confused with this. // confused with this.
// //
// Also, DoCoMo's specification requires vCard composer to use just the first // Also, DoCoMo's specification requires vCard composer to use just the first
// column. // column.
// i.e. // i.e.
// o SOUND;X-IRMC-N:Miyakawa Daisuke;;;; // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
// x SOUND;X-IRMC-N:Miyakawa;Daisuke;;; // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
mBuilder.append(VCardConstants.PROPERTY_SOUND); mBuilder.append(VCardConstants.PROPERTY_SOUND);
mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N); mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,13 +706,14 @@ public class VCardBuilder {
mBuilder.append(encodedPhoneticGivenName); mBuilder.append(encodedPhoneticGivenName);
} }
} }
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} }
Log.d("@@@", "hoge");
if (mUsesDefactProperty) { if (mUsesDefactProperty) {
if (!TextUtils.isEmpty(phoneticGivenName)) { if (!TextUtils.isEmpty(phoneticGivenName)) {
final boolean reallyUseQuotedPrintable = final boolean reallyUseQuotedPrintable =
@@ -549,7 +737,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticGivenName); mBuilder.append(encodedPhoneticGivenName);
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticMiddleName)) { if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable = final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable && (mShouldUseQuotedPrintable &&
@@ -572,7 +760,7 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticMiddleName); mBuilder.append(encodedPhoneticMiddleName);
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticFamilyName)) { if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable = final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable && (mShouldUseQuotedPrintable &&
@@ -595,13 +783,13 @@ public class VCardBuilder {
mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticFamilyName); mBuilder.append(encodedPhoneticFamilyName);
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} } // if (!TextUtils.isEmpty(phoneticFamilyName))
} }
} }
public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) { public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
final boolean useAndroidProperty; final boolean useAndroidProperty;
if (mIsV30) { if (mIsV30OrV40) { // These specifications have NICKNAME property.
useAndroidProperty = false; useAndroidProperty = false;
} else if (mUsesAndroidProperty) { } else if (mUsesAndroidProperty) {
useAndroidProperty = true; useAndroidProperty = true;
@@ -922,21 +1110,21 @@ public class VCardBuilder {
encodedCountry = escapeCharacters(rawCountry); encodedCountry = escapeCharacters(rawCountry);
encodedNeighborhood = escapeCharacters(rawNeighborhood); encodedNeighborhood = escapeCharacters(rawNeighborhood);
} }
final StringBuffer addressBuffer = new StringBuffer(); final StringBuilder addressBuilder = new StringBuilder();
addressBuffer.append(encodedPoBox); addressBuilder.append(encodedPoBox);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
addressBuffer.append(encodedStreet); addressBuilder.append(encodedStreet);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
addressBuffer.append(encodedLocality); addressBuilder.append(encodedLocality);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
addressBuffer.append(encodedRegion); addressBuilder.append(encodedRegion);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
addressBuffer.append(encodedPostalCode); addressBuilder.append(encodedPostalCode);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
addressBuffer.append(encodedCountry); addressBuilder.append(encodedCountry);
return new PostalStruct( return new PostalStruct(
reallyUseQuotedPrintable, appendCharset, addressBuffer.toString()); reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} else { // VCardUtils.areAllEmpty(rawAddressArray) == true } else { // VCardUtils.areAllEmpty(rawAddressArray) == true
// Try to use FORMATTED_ADDRESS instead. // Try to use FORMATTED_ADDRESS instead.
final String rawFormattedAddress = final String rawFormattedAddress =
@@ -959,16 +1147,16 @@ public class VCardBuilder {
// We use the second value ("Extended Address") just because Japanese mobile phones // We use the second value ("Extended Address") just because Japanese mobile phones
// do so. If the other importer expects the value be in the other field, some flag may // do so. If the other importer expects the value be in the other field, some flag may
// be needed. // be needed.
final StringBuffer addressBuffer = new StringBuffer(); final StringBuilder addressBuilder = new StringBuilder();
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
addressBuffer.append(encodedFormattedAddress); addressBuilder.append(encodedFormattedAddress);
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
addressBuffer.append(VCARD_ITEM_SEPARATOR); addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
return new PostalStruct( return new PostalStruct(
reallyUseQuotedPrintable, appendCharset, addressBuffer.toString()); reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} }
} }
@@ -1108,7 +1296,8 @@ public class VCardBuilder {
Log.d(LOG_TAG, "Unknown photo type. Ignored."); Log.d(LOG_TAG, "Unknown photo type. Ignored.");
continue; continue;
} }
final String photoString = new String(Base64.encodeBase64(data)); // TODO: check this works fine.
final String photoString = new String(Base64.encode(data, Base64.NO_WRAP));
if (!TextUtils.isEmpty(photoString)) { if (!TextUtils.isEmpty(photoString)) {
appendPhotoLine(photoString, photoType); appendPhotoLine(photoString, photoType);
} }
@@ -1165,6 +1354,8 @@ public class VCardBuilder {
} }
public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) { public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) {
// There's possibility where a given object may have more than one birthday, which
// is inappropriate. We just build one birthday.
if (contentValuesList != null) { if (contentValuesList != null) {
String primaryBirthday = null; String primaryBirthday = null;
String secondaryBirthday = null; String secondaryBirthday = null;
@@ -1232,16 +1423,19 @@ public class VCardBuilder {
return this; return this;
} }
/**
* @param emitEveryTime If true, builder builds the line even when there's no entry.
*/
public void appendPostalLine(final int type, final String label, public void appendPostalLine(final int type, final String label,
final ContentValues contentValues, final ContentValues contentValues,
final boolean isPrimary, final boolean emitLineEveryTime) { final boolean isPrimary, final boolean emitEveryTime) {
final boolean reallyUseQuotedPrintable; final boolean reallyUseQuotedPrintable;
final boolean appendCharset; final boolean appendCharset;
final String addressValue; final String addressValue;
{ {
PostalStruct postalStruct = tryConstructPostalStruct(contentValues); PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
if (postalStruct == null) { if (postalStruct == null) {
if (emitLineEveryTime) { if (emitEveryTime) {
reallyUseQuotedPrintable = false; reallyUseQuotedPrintable = false;
appendCharset = false; appendCharset = false;
addressValue = ""; addressValue = "";
@@ -1463,7 +1657,7 @@ public class VCardBuilder {
parameterList.add(VCardConstants.PARAM_TYPE_VOICE); parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
} else if (VCardUtils.isMobilePhoneLabel(label)) { } else if (VCardUtils.isMobilePhoneLabel(label)) {
parameterList.add(VCardConstants.PARAM_TYPE_CELL); parameterList.add(VCardConstants.PARAM_TYPE_CELL);
} else if (mIsV30) { } else if (mIsV30OrV40) {
// This label is appropriately encoded in appendTypeParameters. // This label is appropriately encoded in appendTypeParameters.
parameterList.add(label); parameterList.add(label);
} else { } else {
@@ -1526,8 +1720,8 @@ public class VCardBuilder {
StringBuilder tmpBuilder = new StringBuilder(); StringBuilder tmpBuilder = new StringBuilder();
tmpBuilder.append(VCardConstants.PROPERTY_PHOTO); tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
tmpBuilder.append(VCARD_PARAM_SEPARATOR); tmpBuilder.append(VCARD_PARAM_SEPARATOR);
if (mIsV30) { if (mIsV30OrV40) {
tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30); tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_AS_B);
} else { } else {
tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21); tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
} }
@@ -1559,7 +1753,8 @@ public class VCardBuilder {
mBuilder.append(VCARD_END_OF_LINE); mBuilder.append(VCARD_END_OF_LINE);
} }
public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) { public void appendAndroidSpecificProperty(
final String mimeType, ContentValues contentValues) {
if (!sAllowedAndroidPropertySet.contains(mimeType)) { if (!sAllowedAndroidPropertySet.contains(mimeType)) {
return; return;
} }
@@ -1681,7 +1876,7 @@ public class VCardBuilder {
encodedValue = encodeQuotedPrintable(rawValue); encodedValue = encodeQuotedPrintable(rawValue);
} else { } else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though // TODO: one line may be too huge, which may be invalid in vCard spec, though
// several (even well-known) applications do not care this. // several (even well-known) applications do not care that violation.
encodedValue = escapeCharacters(rawValue); encodedValue = escapeCharacters(rawValue);
} }
@@ -1744,7 +1939,12 @@ public class VCardBuilder {
// which would be recommended way in vcard 3.0 though not valid in vCard 2.1. // which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
boolean first = true; boolean first = true;
for (final String typeValue : types) { for (final String typeValue : types) {
if (VCardConfig.isV30(mVCardType)) { if (VCardConfig.isVersion30(mVCardType)) {
final String encoded = VCardUtils.toStringAsV30ParamValue(typeValue);
if (TextUtils.isEmpty(encoded)) {
continue;
}
// Note: vCard 3.0 specifies the different type of acceptable type Strings, but // Note: vCard 3.0 specifies the different type of acceptable type Strings, but
// we don't emit that kind of vCard 3.0 specific type since there should be // we don't emit that kind of vCard 3.0 specific type since there should be
// high probabilyty in which external importers cannot understand them. // high probabilyty in which external importers cannot understand them.
@@ -1756,7 +1956,7 @@ public class VCardBuilder {
} else { } else {
mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCARD_PARAM_SEPARATOR);
} }
appendTypeParameter(VCardUtils.toStringAvailableAsV30ParameValue(typeValue)); appendTypeParameter(encoded);
} else { // vCard 2.1 } else { // vCard 2.1
if (!VCardUtils.isV21Word(typeValue)) { if (!VCardUtils.isV21Word(typeValue)) {
continue; continue;
@@ -1783,7 +1983,8 @@ public class VCardBuilder {
// device is DoCoMo's (just for safety). // device is DoCoMo's (just for safety).
// //
// Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF" // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF"
if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) { if (VCardConfig.isVersion40(mVCardType) ||
((VCardConfig.isVersion30(mVCardType) || mAppendTypeParamName) && !mIsDoCoMo)) {
builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL); builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL);
} }
builder.append(type); builder.append(type);
@@ -1825,9 +2026,9 @@ public class VCardBuilder {
byte[] strArray = null; byte[] strArray = null;
try { try {
strArray = str.getBytes(mCharsetString); strArray = str.getBytes(mCharset);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. " Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ "Try default charset"); + "Try default charset");
strArray = str.getBytes(); strArray = str.getBytes();
} }
@@ -1893,7 +2094,7 @@ public class VCardBuilder {
break; break;
} }
case '\\': { case '\\': {
if (mIsV30) { if (mIsV30OrV40) {
tmpBuilder.append("\\\\"); tmpBuilder.append("\\\\");
break; break;
} else { } else {
@@ -1911,7 +2112,7 @@ public class VCardBuilder {
break; break;
} }
case ',': { case ',': {
if (mIsV30) { if (mIsV30OrV40) {
tmpBuilder.append("\\,"); tmpBuilder.append("\\,");
} else { } else {
tmpBuilder.append(ch); tmpBuilder.append(ch);

View File

@@ -19,16 +19,12 @@ import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Entity; import android.content.Entity;
import android.content.EntityIterator;
import android.content.Entity.NamedContentValues; import android.content.Entity.NamedContentValues;
import android.content.EntityIterator;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteException;
import android.net.Uri; import android.net.Uri;
import android.pim.vcard.exception.VCardException; import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -41,6 +37,11 @@ import android.provider.ContactsContract.CommonDataKinds.Relation;
import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
import android.text.TextUtils;
import android.util.CharsetUtils; import android.util.CharsetUtils;
import android.util.Log; import android.util.Log;
@@ -61,15 +62,11 @@ import java.util.Map;
/** /**
* <p> * <p>
* The class for composing VCard from Contacts information. Note that this is * The class for composing vCard from Contacts information.
* completely differnt implementation from
* android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
* </p> * </p>
*
* <p> * <p>
* Usually, this class should be used like this. * Usually, this class should be used like this.
* </p> * </p>
*
* <pre class="prettyprint">VCardComposer composer = null; * <pre class="prettyprint">VCardComposer composer = null;
* try { * try {
* composer = new VCardComposer(context); * composer = new VCardComposer(context);
@@ -93,15 +90,18 @@ import java.util.Map;
* if (composer != null) { * if (composer != null) {
* composer.terminate(); * composer.terminate();
* } * }
* } </pre> * }</pre>
* <p>
* Users have to manually take care of memory efficiency. Even one vCard may contain
* image of non-trivial size for mobile devices.
* </p>
* <p>
* {@link VCardBuilder} is used to build each vCard.
* </p>
*/ */
public class VCardComposer { public class VCardComposer {
private static final String LOG_TAG = "VCardComposer"; private static final String LOG_TAG = "VCardComposer";
public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME;
public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME;
public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER;
public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO = public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO =
"Failed to get database information"; "Failed to get database information";
@@ -119,6 +119,8 @@ public class VCardComposer {
public static final String VCARD_TYPE_STRING_DOCOMO = "docomo"; public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
// Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here,
// since usual vCard devices for Japanese devices already use it.
private static final String SHIFT_JIS = "SHIFT_JIS"; private static final String SHIFT_JIS = "SHIFT_JIS";
private static final String UTF_8 = "UTF-8"; private static final String UTF_8 = "UTF-8";
@@ -141,7 +143,7 @@ public class VCardComposer {
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ); sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER); sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME); sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME);
// Google talk is a special case. // We don't add Google talk here since it has to be handled separately.
} }
public static interface OneEntryHandler { public static interface OneEntryHandler {
@@ -152,37 +154,37 @@ public class VCardComposer {
/** /**
* <p> * <p>
* An useful example handler, which emits VCard String to outputstream one by one. * An useful handler for emitting vCard String to an OutputStream object one by one.
* </p> * </p>
* <p> * <p>
* The input OutputStream object is closed() on {@link #onTerminate()}. * The input OutputStream object is closed() on {@link #onTerminate()}.
* Must not close the stream outside. * Must not close the stream outside this class.
* </p> * </p>
*/ */
public class HandlerForOutputStream implements OneEntryHandler { public final class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding") @SuppressWarnings("hiding")
private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream"; private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
final private OutputStream mOutputStream; // mWriter will close this.
private Writer mWriter;
private boolean mOnTerminateIsCalled = false; private boolean mOnTerminateIsCalled = false;
private final OutputStream mOutputStream; // mWriter will close this.
private Writer mWriter;
/** /**
* Input stream will be closed on the detruction of this object. * Input stream will be closed on the detruction of this object.
*/ */
public HandlerForOutputStream(OutputStream outputStream) { public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream; mOutputStream = outputStream;
} }
public boolean onInit(Context context) { public boolean onInit(final Context context) {
try { try {
mWriter = new BufferedWriter(new OutputStreamWriter( mWriter = new BufferedWriter(new OutputStreamWriter(
mOutputStream, mCharsetString)); mOutputStream, mCharset));
} catch (UnsupportedEncodingException e1) { } catch (UnsupportedEncodingException e1) {
Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString); Log.e(LOG_TAG, "Unsupported charset: " + mCharset);
mErrorReason = "Encoding is not supported (usually this does not happen!): " mErrorReason = "Encoding is not supported (usually this does not happen!): "
+ mCharsetString; + mCharset;
return false; return false;
} }
@@ -235,14 +237,19 @@ public class VCardComposer {
"IOException during closing the output stream: " "IOException during closing the output stream: "
+ e.getMessage()); + e.getMessage());
} finally { } finally {
try { closeOutputStream();
mWriter.close();
} catch (IOException e) {
}
} }
} }
} }
public void closeOutputStream() {
try {
mWriter.close();
} catch (IOException e) {
Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
}
}
@Override @Override
public void finalize() { public void finalize() {
if (!mOnTerminateIsCalled) { if (!mOnTerminateIsCalled) {
@@ -257,11 +264,10 @@ public class VCardComposer {
private final ContentResolver mContentResolver; private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo; private final boolean mIsDoCoMo;
private final boolean mUsesShiftJis;
private Cursor mCursor; private Cursor mCursor;
private int mIdColumn; private int mIdColumn;
private final String mCharsetString; private final String mCharset;
private boolean mTerminateIsCalled; private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList; private final List<OneEntryHandler> mHandlerList;
@@ -272,21 +278,39 @@ public class VCardComposer {
}; };
public VCardComposer(Context context) { public VCardComposer(Context context) {
this(context, VCardConfig.VCARD_TYPE_DEFAULT, true); this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
} }
/**
* The variant which sets charset to null and sets careHandlerErrors to true.
*/
public VCardComposer(Context context, int vcardType) { public VCardComposer(Context context, int vcardType) {
this(context, vcardType, true); this(context, vcardType, null, true);
} }
public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) { public VCardComposer(Context context, int vcardType, String charset) {
this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors); this(context, vcardType, charset, true);
}
/**
* The variant which sets charset to null.
*/
public VCardComposer(final Context context, final int vcardType,
final boolean careHandlerErrors) {
this(context, vcardType, null, careHandlerErrors);
} }
/** /**
* Construct for supporting call log entry vCard composing. * Construct for supporting call log entry vCard composing.
*
* @param context Context to be used during the composition.
* @param vcardType The type of vCard, typically available via {@link VCardConfig}.
* @param charset The charset to be used. Use null when you don't need the charset.
* @param careHandlerErrors If true, This object returns false everytime
* a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false.
* If false, this ignores those errors.
*/ */
public VCardComposer(final Context context, final int vcardType, public VCardComposer(final Context context, final int vcardType, String charset,
final boolean careHandlerErrors) { final boolean careHandlerErrors) {
mContext = context; mContext = context;
mVCardType = vcardType; mVCardType = vcardType;
@@ -294,30 +318,67 @@ public class VCardComposer {
mContentResolver = context.getContentResolver(); mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>(); mHandlerList = new ArrayList<OneEntryHandler>();
if (mIsDoCoMo) { charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
String charset; final boolean shouldAppendCharsetParam = !(
try { VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
} catch (UnsupportedCharsetException e) { if (mIsDoCoMo || shouldAppendCharsetParam) {
Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); if (SHIFT_JIS.equalsIgnoreCase(charset)) {
charset = SHIFT_JIS; if (mIsDoCoMo) {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"DoCoMo-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
} else {
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG,
"Career-specific SHIFT_JIS was not found. "
+ "Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
}
mCharset = charset;
} else {
Log.w(LOG_TAG,
"The charset \"" + charset + "\" is used while "
+ SHIFT_JIS + " is needed to be used.");
if (TextUtils.isEmpty(charset)) {
mCharset = SHIFT_JIS;
} else {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
}
} }
mCharsetString = charset;
} else if (mUsesShiftJis) {
String charset;
try {
charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name();
} catch (UnsupportedCharsetException e) {
Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is.");
charset = SHIFT_JIS;
}
mCharsetString = charset;
} else { } else {
mCharsetString = UTF_8; if (TextUtils.isEmpty(charset)) {
mCharset = UTF_8;
} else {
try {
charset = CharsetUtils.charsetForVendor(charset).name();
} catch (UnsupportedCharsetException e) {
Log.i(LOG_TAG,
"Career-specific \"" + charset + "\" was not found (as usual). "
+ "Use it as is.");
}
mCharset = charset;
}
} }
Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
} }
/** /**
@@ -351,7 +412,7 @@ public class VCardComposer {
} }
if (mCareHandlerErrors) { if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size()); mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) { for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) { if (!handler.onInit(mContext)) {
@@ -414,7 +475,7 @@ public class VCardComposer {
mErrorReason = FAILURE_REASON_NOT_INITIALIZED; mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false; return false;
} }
String vcard; final String vcard;
try { try {
if (mIdColumn >= 0) { if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn), vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +498,7 @@ public class VCardComposer {
mCursor.moveToNext(); mCursor.moveToNext();
} }
// This function does not care the OutOfMemoryError on the handler side // This function does not care the OutOfMemoryError on the handler side :-P
// :-P
if (mCareHandlerErrors) { if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size()); mHandlerList.size());
@@ -457,7 +517,7 @@ public class VCardComposer {
} }
private String createOneEntryInternal(final String contactId, private String createOneEntryInternal(final String contactId,
Method getEntityIteratorMethod) throws VCardException { final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap = final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>(); new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible. // The resolver may return the entity iterator with no data. It is possible.
@@ -466,12 +526,13 @@ public class VCardComposer {
EntityIterator entityIterator = null; EntityIterator entityIterator = null;
try { try {
final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon() final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
// .appendQueryParameter("for_export_only", "1")
.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1") .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
.build(); .build();
final String selection = Data.CONTACT_ID + "=?"; final String selection = Data.CONTACT_ID + "=?";
final String[] selectionArgs = new String[] {contactId}; final String[] selectionArgs = new String[] {contactId};
if (getEntityIteratorMethod != null) { if (getEntityIteratorMethod != null) {
// Please note that this branch is executed by some tests only // Please note that this branch is executed by unit tests only
try { try {
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null, entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, null); mContentResolver, uri, selection, selectionArgs, null);
@@ -527,22 +588,33 @@ public class VCardComposer {
} }
} }
final VCardBuilder builder = new VCardBuilder(mVCardType); return buildVCard(contentValuesListMap);
builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE)) }
.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE)) /**
.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE)) * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
.appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE)) * {ContactsContract}. Developers can override this method to customize the output.
.appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE)) */
.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE)); public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) { if (contentValuesListMap == null) {
builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE)); Log.e(LOG_TAG, "The given map is null. Ignore and return empty String");
return "";
} else {
final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset);
builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE))
.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE))
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE))
.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE))
.appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE))
.appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE))
.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE))
.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE))
.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
} }
builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE))
.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE))
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
return builder.toString();
} }
public void terminate() { public void terminate() {
@@ -565,26 +637,38 @@ public class VCardComposer {
@Override @Override
public void finalize() { public void finalize() {
if (!mTerminateIsCalled) { if (!mTerminateIsCalled) {
Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
terminate(); terminate();
} }
} }
/**
* @return returns the number of available entities. The return value is undefined
* when this object is not ready yet (typically when {{@link #init()} is not called
* or when {@link #terminate()} is already called).
*/
public int getCount() { public int getCount() {
if (mCursor == null) { if (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return 0; return 0;
} }
return mCursor.getCount(); return mCursor.getCount();
} }
/**
* @return true when there's no entity to be built. The return value is undefined
* when this object is not ready yet.
*/
public boolean isAfterLast() { public boolean isAfterLast() {
if (mCursor == null) { if (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return false; return false;
} }
return mCursor.isAfterLast(); return mCursor.isAfterLast();
} }
/** /**
* @return Return the error reason if possible. * @return Returns the error reason.
*/ */
public String getErrorReason() { public String getErrorReason() {
return mErrorReason; return mErrorReason;

View File

@@ -38,20 +38,43 @@ public class VCardConfig {
/* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE; /* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE;
/* package */ static final int PARSE_TYPE_UNKNOWN = 0; /**
/* package */ static final int PARSE_TYPE_APPLE = 1; * <p>
/* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones. * The charset used during import.
/* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones. * </p>
/* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4; * <p>
* We cannot determine which charset should be used to interpret lines in vCard,
* while Java requires us to specify it when InputStream is used.
* We need to rely on the mechanism due to some performance reason.
* </p>
* <p>
* In order to avoid "misinterpretation" of charset and lose any data in vCard,
* "ISO-8859-1" is first used for reading the stream.
* When a charset is specified in a property (with "CHARSET=..." parameter),
* the string is decoded to raw bytes and encoded into the specific charset,
* </p>
* <p>
* Unicode specification there's a one to one mapping between each byte in ISO-8859-1
* and a codepoint, and Java specification requires runtime must have the charset.
* Thus, ISO-8859-1 is one effective mapping for intermediate mapping.
* </p>
*/
public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1";
// Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and /**
// decode the unicode to the original charset. If not, this setting will cause some bug. * The charset used when there's no information affbout what charset should be used to
public static final String DEFAULT_CHARSET = "iso-8859-1"; * encode the binary given from vCard.
*/
public static final int FLAG_V21 = 0; public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
public static final int FLAG_V30 = 1; public static final String DEFAULT_EXPORT_CHARSET = "UTF-8";
// 0x2 is reserved for the future use ... /**
* Do not use statically like "version == VERSION_V21"
*/
public static final int VERSION_21 = 0;
public static final int VERSION_30 = 1;
public static final int VERSION_40 = 2;
public static final int VERSION_MASK = 3;
public static final int NAME_ORDER_DEFAULT = 0; public static final int NAME_ORDER_DEFAULT = 0;
public static final int NAME_ORDER_EUROPE = 0x4; public static final int NAME_ORDER_EUROPE = 0x4;
@@ -59,144 +82,140 @@ public class VCardConfig {
private static final int NAME_ORDER_MASK = 0xC; private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety // 0x10 is reserved for safety
private static final int FLAG_CHARSET_UTF8 = 0;
private static final int FLAG_CHARSET_SHIFT_JIS = 0x100;
private static final int FLAG_CHARSET_MASK = 0xF00;
/** /**
* <p>
* The flag indicating the vCard composer will add some "X-" properties used only in Android * The flag indicating the vCard composer will add some "X-" properties used only in Android
* when the formal vCard specification does not have appropriate fields for that data. * when the formal vCard specification does not have appropriate fields for that data.
* * </p>
* <p>
* For example, Android accepts nickname information while vCard 2.1 does not. * For example, Android accepts nickname information while vCard 2.1 does not.
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME") * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
* instead of just dropping it. * instead of just dropping it.
* * </p>
* <p>
* vCard parser code automatically parses the field emitted even when this flag is off. * vCard parser code automatically parses the field emitted even when this flag is off.
* * </p>
* Note that this flag does not assure all the information must be hold in the emitted vCard.
*/ */
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000; private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
/** /**
* <p>
* The flag indicating the vCard composer will add some "X-" properties seen in the * The flag indicating the vCard composer will add some "X-" properties seen in the
* vCard data emitted by the other softwares/devices when the formal vCard specification * vCard data emitted by the other softwares/devices when the formal vCard specification
* does not have appropriate field(s) for that data. * does not have appropriate field(s) for that data.
* * </p>
* <p>
* One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
* non-Android devices/softwares. We chose to enable the vCard composer to use those * non-Android devices/softwares. We chose to enable the vCard composer to use those
* defact properties since they are also useful for Android devices. * defact properties since they are also useful for Android devices.
* * </p>
* <p>
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0 * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties. * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
* </p>
*/ */
private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000; private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000;
/** /**
* The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese * <p>
* The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese
* mobile careers) should be used. This flag does not include any other information like * mobile careers) should be used. This flag does not include any other information like
* that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's
* dialect but the name order should be European", but it is not recommended. * dialect but the name order should be European", but it is not recommended.
* </p>
*/ */
private static final int FLAG_DOCOMO = 0x20000000; private static final int FLAG_DOCOMO = 0x20000000;
/** /**
* <P> * <p>
* The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary" * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary"
* properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0). * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0).
* </P> * </p>
* <P> * <p>
* We actually cannot define what is the "primary" property. Note that this is NOT defined * We actually cannot define what is the "primary" property. Note that this is NOT defined
* in vCard specification either. Also be aware that it is NOT related to "primary" notion * in vCard specification either. Also be aware that it is NOT related to "primary" notion
* used in {@link android.provider.ContactsContract}. * used in {@link android.provider.ContactsContract}.
* This notion is just for vCard composition in Android. * This notion is just for vCard composition in Android.
* </P> * </p>
* <P> * <p>
* We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1 * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1
* do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc. * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc.
* even when their values contain non-ascii or/and CR/LF, while they use the encoding in the * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the
* other properties like "ADR", "ORG", etc. * other properties like "ADR", "ORG", etc.
* <P> * <p>
* We are afraid of the case where some vCard importer also forget handling QP presuming QP is * We are afraid of the case where some vCard importer also forget handling QP presuming QP is
* not used in such fields. * not used in such fields.
* </P> * </p>
* <P> * <p>
* This flag is useful when some target importer you are going to focus on does not accept * This flag is useful when some target importer you are going to focus on does not accept
* such properties with Quoted-Printable encoding. * such properties with Quoted-Printable encoding.
* </P> * </p>
* <P> * <p>
* Again, we should not use this flag at all for complying vCard 2.1 spec. * Again, we should not use this flag at all for complying vCard 2.1 spec.
* </P> * </p>
* <P> * <p>
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
* kind of problem (hopefully). * kind of problem (hopefully).
* </P> * </p>
* @hide
*/ */
public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000; public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000;
/** /**
* <P> * <p>
* The flag indicating that phonetic name related fields must be converted to * The flag indicating that phonetic name related fields must be converted to
* appropriate form. Note that "appropriate" is not defined in any vCard specification. * appropriate form. Note that "appropriate" is not defined in any vCard specification.
* This is Android-specific. * This is Android-specific.
* </P> * </p>
* <P> * <p>
* One typical (and currently sole) example where we need this flag is the time when * One typical (and currently sole) example where we need this flag is the time when
* we need to emit Japanese phonetic names into vCard entries. The property values * we need to emit Japanese phonetic names into vCard entries. The property values
* should be encoded into half-width katakana when the target importer is Japanese mobile * should be encoded into half-width katakana when the target importer is Japanese mobile
* phones', which are probably not able to parse full-width hiragana/katakana for * phones', which are probably not able to parse full-width hiragana/katakana for
* historical reasons, while the vCard importers embedded to softwares for PC should be * historical reasons, while the vCard importers embedded to softwares for PC should be
* able to parse them as we expect. * able to parse them as we expect.
* </P> * </p>
*/ */
public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000; public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000;
/** /**
* <P> * <p>
* The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params
* every time possible. The default behavior does not emit it and is valid in the spec. * every time possible. The default behavior does not emit it and is valid in the spec.
* In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification. * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification.
* </P> * </p>
* <P> * <p>
* Detail: * Detail:
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0. * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
* </p> * </p>
* <P> * <p>
* e.g.<BR /> * e.g.
* 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR /> * </p>
* 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR /> * <ol>
* 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR /> * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
* </P> * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
* <P> * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
* 2) had been the default of VCard exporter/importer in Android, but it is found that * </ol>
* some external exporter is not able to parse the type format like 2) but only 3). * <p>
* </P>
* <P>
* If you are targeting to the importer which cannot accept TYPE params without "TYPE=" * If you are targeting to the importer which cannot accept TYPE params without "TYPE="
* strings (which should be rare though), please use this flag. * strings (which should be rare though), please use this flag.
* </P> * </p>
* <P> * <p>
* Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM); * Example usage:
* </P> * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
* </p>
*/ */
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000; public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
/** /**
* <P> * <p>
* The flag asking exporter to refrain image export.
* </P>
* @hide will be deleted in the near future.
*/
public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000;
/**
* <P>
* The flag indicating the vCard composer does touch nothing toward phone number Strings * The flag indicating the vCard composer does touch nothing toward phone number Strings
* but leave it as is. * but leave it as is.
* </P> * </p>
* <P> * <p>
* The vCard specifications mention nothing toward phone numbers, while some devices * The vCard specifications mention nothing toward phone numbers, while some devices
* do (wrongly, but with innevitable reasons). * do (wrongly, but with innevitable reasons).
* For example, there's a possibility Japanese mobile phones are expected to have * For example, there's a possibility Japanese mobile phones are expected to have
@@ -207,187 +226,194 @@ public class VCardConfig {
* becomes "111-222-3333"). * becomes "111-222-3333").
* Unfortunate side effect of that use was some control characters used in the other * Unfortunate side effect of that use was some control characters used in the other
* areas may be badly affected by the formatting. * areas may be badly affected by the formatting.
* </P> * </p>
* <P> * <p>
* This flag disables that formatting, affecting both importer and exporter. * This flag disables that formatting, affecting both importer and exporter.
* If the user is aware of some side effects due to the implicit formatting, use this flag. * If the user is aware of some side effects due to the implicit formatting, use this flag.
* </P> * </p>
*/ */
public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000; public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000;
/**
* <p>
* For importer only. Ignored in exporter.
* </p>
* <p>
* The flag indicating the parser should handle a nested vCard, in which vCard clause starts
* in another vCard clause. Here's a typical example.
* </p>
* <pre class="prettyprint">BEGIN:VCARD
* BEGIN:VCARD
* VERSION:2.1
* ...
* END:VCARD
* END:VCARD</pre>
* <p>
* The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries,
* while some mobile devices emit nested ones as primary data to be imported.
* </p>
* <p>
* This flag forces a vCard parser to torelate such a nest and understand its content.
* </p>
*/
public static final int FLAG_TORELATE_NEST = 0x01000000;
//// The followings are VCard types available from importer/exporter. //// //// The followings are VCard types available from importer/exporter. ////
/** /**
* <P> * <p>
* Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset. * The type indicating nothing. Used by {@link VCardSourceDetector} when it
* When composing a vCard entry, the US convension will be used toward formatting * was not able to guess the exact vCard type.
* some values. * </p>
* </P> */
* <P> public static final int VCARD_TYPE_UNKNOWN = 0;
/**
* <p>
* Generic vCard format with the vCard 2.1. When composing a vCard entry,
* the US convension will be used toward formatting some values.
* </p>
* <p>
* e.g. The order of the display name would be "Prefix Given Middle Family Suffix", * e.g. The order of the display name would be "Prefix Given Middle Family Suffix",
* while it should be "Prefix Family Middle Given Suffix" in Japan for example. * while it should be "Prefix Family Middle Given Suffix" in Japan for example.
* </P> * </p>
* <p>
* Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer
* outside Android cannot accept it since vCard 2.1 specifically does not allow
* that charset, while we need to use it to support various languages around the world.
* </p>
* <p>
* If you want to use alternative charset, you should notify the charset to the other
* compontent to be used.
* </p>
*/ */
public static final int VCARD_TYPE_V21_GENERIC_UTF8 = public static final int VCARD_TYPE_V21_GENERIC =
(FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | (VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic"; /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic";
/** /**
* <P> * <p>
* General vCard format with the version 3.0. Uses UTF-8 for the charset. * General vCard format with the version 3.0. Uses UTF-8 for the charset.
* </P> * </p>
* <P> * <p>
* Not fully ready yet. Use with caution when you use this. * Not fully ready yet. Use with caution when you use this.
* </P> * </p>
*/ */
public static final int VCARD_TYPE_V30_GENERIC_UTF8 = public static final int VCARD_TYPE_V30_GENERIC =
(FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | (VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic";
/* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
/** /**
* <P> * General vCard format with the version 4.0.
* @hide vCard 4.0 is not published yet.
*/
public static final int VCARD_TYPE_V40_GENERIC =
(VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_generic";
/**
* <p>
* General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8. * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix") * Currently, only name order is considered ("Prefix Middle Given Family Suffix")
* </P> * </p>
*/ */
public static final int VCARD_TYPE_V21_EUROPE_UTF8 = public static final int VCARD_TYPE_V21_EUROPE =
(FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | (VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
/* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
/** /**
* <P> * <p>
* General vCard format with the version 3.0 with some Europe convension. Uses UTF-8. * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8.
* </P> * </p>
* <P> * <p>
* Not ready yet. Use with caution when you use this. * Not ready yet. Use with caution when you use this.
* </P> * </p>
*/ */
public static final int VCARD_TYPE_V30_EUROPE_UTF8 = public static final int VCARD_TYPE_V30_EUROPE =
(FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | (VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe"; /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe";
/** /**
* <P> * <p>
* The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset. * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset.
* </P> * </p>
* <P> * <p>
* Not ready yet. Use with caution when you use this. * Not ready yet. Use with caution when you use this.
* </P> * </p>
*/ */
public static final int VCARD_TYPE_V21_JAPANESE_UTF8 = public static final int VCARD_TYPE_V21_JAPANESE =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | (VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8"; /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8";
/** /**
* <P> * <p>
* vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for
* parsing/composing the vCard data.
* </P>
* <P>
* Not ready yet. Use with caution when you use this.
* </P>
*/
public static final int VCARD_TYPE_V21_JAPANESE_SJIS =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis";
/**
* <P>
* vCard format for miscellaneous Japanese devices, using Shift_Jis for
* parsing/composing the vCard data.
* </P>
* <P>
* Not ready yet. Use with caution when you use this.
* </P>
*/
public static final int VCARD_TYPE_V30_JAPANESE_SJIS =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis";
/**
* <P>
* The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset. * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset.
* </P> * </p>
* <P> * <p>
* Not ready yet. Use with caution when you use this. * Not ready yet. Use with caution when you use this.
* </P> * </p>
*/ */
public static final int VCARD_TYPE_V30_JAPANESE_UTF8 = public static final int VCARD_TYPE_V30_JAPANESE =
(FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | (VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8"; /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8";
/** /**
* <P> * <p>
* The vCard 2.1 based format which (partially) considers the convention in Japanese * The vCard 2.1 based format which (partially) considers the convention in Japanese
* mobile phones, where phonetic names are translated to half-width katakana if * mobile phones, where phonetic names are translated to half-width katakana if
* possible, etc. * possible, etc. It would be better to use Shift_JIS as a charset for maximum
* </P> * compatibility.
* <P> * </p>
* Not ready yet. Use with caution when you use this. * @hide Should not be available world wide.
* </P>
*/ */
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE = public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
(FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | (VERSION_21 | NAME_ORDER_JAPANESE |
FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile"; /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
/** /**
* <P> * <p>
* VCard format used in DoCoMo, which is one of Japanese mobile phone careers. * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
* </p> * </p>
* <P> * <p>
* Base version is vCard 2.1, but the data has several DoCoMo-specific convensions. * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions.
* No Android-specific property nor defact property is included. The "Primary" properties * No Android-specific property nor defact property is included. The "Primary" properties
* are NOT encoded to Quoted-Printable. * are NOT encoded to Quoted-Printable.
* </P> * </p>
* @hide Should not be available world wide.
*/ */
public static final int VCARD_TYPE_DOCOMO = public static final int VCARD_TYPE_DOCOMO =
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO); (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO);
/* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo"; /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo";
public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8; public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC;
private static final Map<String, Integer> sVCardTypeMap; private static final Map<String, Integer> sVCardTypeMap;
private static final Set<Integer> sJapaneseMobileTypeSet; private static final Set<Integer> sJapaneseMobileTypeSet;
static { static {
sVCardTypeMap = new HashMap<String, Integer>(); sVCardTypeMap = new HashMap<String, Integer>();
sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8); sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8); sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8); sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8); sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS); sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8); sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS);
sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE); sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO); sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
sJapaneseMobileTypeSet = new HashSet<Integer>(); sJapaneseMobileTypeSet = new HashSet<Integer>();
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS); sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8); sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE); sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO); sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
} }
@@ -404,20 +430,20 @@ public class VCardConfig {
} }
} }
public static boolean isV30(final int vcardType) { public static boolean isVersion21(final int vcardType) {
return ((vcardType & FLAG_V30) != 0); return (vcardType & VERSION_MASK) == VERSION_21;
}
public static boolean isVersion30(final int vcardType) {
return (vcardType & VERSION_MASK) == VERSION_30;
}
public static boolean isVersion40(final int vcardType) {
return (vcardType & VERSION_MASK) == VERSION_40;
} }
public static boolean shouldUseQuotedPrintable(final int vcardType) { public static boolean shouldUseQuotedPrintable(final int vcardType) {
return !isV30(vcardType); return !isVersion30(vcardType);
}
public static boolean usesUtf8(final int vcardType) {
return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8);
}
public static boolean usesShiftJis(final int vcardType) {
return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS);
} }
public static int getNameOrderType(final int vcardType) { public static int getNameOrderType(final int vcardType) {
@@ -442,7 +468,7 @@ public class VCardConfig {
} }
public static boolean appendTypeParamName(final int vcardType) { public static boolean appendTypeParamName(final int vcardType) {
return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0)); return (isVersion30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0));
} }
/** /**
@@ -474,4 +500,4 @@ public class VCardConfig {
private VCardConfig() { private VCardConfig() {
} }
} }

View File

@@ -21,6 +21,7 @@ package android.pim.vcard;
public class VCardConstants { public class VCardConstants {
public static final String VERSION_V21 = "2.1"; public static final String VERSION_V21 = "2.1";
public static final String VERSION_V30 = "3.0"; public static final String VERSION_V30 = "3.0";
public static final String VERSION_V40 = "4.0";
// The property names valid both in vCard 2.1 and 3.0. // The property names valid both in vCard 2.1 and 3.0.
public static final String PROPERTY_BEGIN = "BEGIN"; public static final String PROPERTY_BEGIN = "BEGIN";
@@ -38,25 +39,30 @@ public class VCardConstants {
public static final String PROPERTY_PHOTO = "PHOTO"; public static final String PROPERTY_PHOTO = "PHOTO";
public static final String PROPERTY_LOGO = "LOGO"; public static final String PROPERTY_LOGO = "LOGO";
public static final String PROPERTY_URL = "URL"; public static final String PROPERTY_URL = "URL";
public static final String PROPERTY_BDAY = "BDAY"; // Birthday public static final String PROPERTY_BDAY = "BDAY"; // Birthday (3.0, 4.0)
public static final String PROPERTY_BIRTH = "BIRTH"; // Place of birth (4.0)
public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY"; // Date of marriage (4.0)
public static final String PROPERTY_NAME = "NAME"; // (3.0, 4,0)
public static final String PROPERTY_NICKNAME = "NICKNAME"; // (3.0, 4.0)
public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // (3.0, 4.0)
public static final String PROPERTY_END = "END"; public static final String PROPERTY_END = "END";
// Valid property names not supported (not appropriately handled) by our vCard importer now. // Valid property names not supported (not appropriately handled) by our importer.
// TODO: Should be removed from the view of memory efficiency?
public static final String PROPERTY_REV = "REV"; public static final String PROPERTY_REV = "REV";
public static final String PROPERTY_AGENT = "AGENT"; public static final String PROPERTY_AGENT = "AGENT"; // (3.0)
public static final String PROPERTY_DDAY = "DDAY"; // Date of death (4.0)
public static final String PROPERTY_DEATH = "DEATH"; // Place of death (4.0)
// Available in vCard 3.0. Shoud not use when composing vCard 2.1 file. // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
public static final String PROPERTY_NAME = "NAME";
public static final String PROPERTY_NICKNAME = "NICKNAME";
public static final String PROPERTY_SORT_STRING = "SORT-STRING";
// De-fact property values expressing phonetic names. // De-fact property values expressing phonetic names.
public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME"; public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME"; public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME"; public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";
// Properties both ContactsStruct in Eclair and de-fact vCard extensions // Properties both ContactsStruct and de-fact vCard extensions
// shown in http://en.wikipedia.org/wiki/VCard support are defined here. // Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
public static final String PROPERTY_X_AIM = "X-AIM"; public static final String PROPERTY_X_AIM = "X-AIM";
public static final String PROPERTY_X_MSN = "X-MSN"; public static final String PROPERTY_X_MSN = "X-MSN";
public static final String PROPERTY_X_YAHOO = "X-YAHOO"; public static final String PROPERTY_X_YAHOO = "X-YAHOO";
@@ -89,6 +95,9 @@ public class VCardConstants {
public static final String PARAM_TYPE_VOICE = "VOICE"; public static final String PARAM_TYPE_VOICE = "VOICE";
public static final String PARAM_TYPE_INTERNET = "INTERNET"; public static final String PARAM_TYPE_INTERNET = "INTERNET";
public static final String PARAM_CHARSET = "CHARSET";
public static final String PARAM_ENCODING = "ENCODING";
// Abbreviation of "prefered" according to vCard 2.1 specification. // Abbreviation of "prefered" according to vCard 2.1 specification.
// We interpret this value as "primary" property during import/export. // We interpret this value as "primary" property during import/export.
// //
@@ -109,6 +118,12 @@ public class VCardConstants {
public static final String PARAM_TYPE_BBS = "BBS"; public static final String PARAM_TYPE_BBS = "BBS";
public static final String PARAM_TYPE_VIDEO = "VIDEO"; public static final String PARAM_TYPE_VIDEO = "VIDEO";
public static final String PARAM_ENCODING_7BIT = "7BIT";
public static final String PARAM_ENCODING_8BIT = "8BIT";
public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1
public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0
// TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1). // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
// These types are basically encoded to "X-" parameters when composing vCard. // These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not. // Parser passes these when "X-" is added to the parameter or not.
@@ -126,14 +141,15 @@ public class VCardConstants {
public static final String PARAM_ADR_TYPE_DOM = "DOM"; public static final String PARAM_ADR_TYPE_DOM = "DOM";
public static final String PARAM_ADR_TYPE_INTL = "INTL"; public static final String PARAM_ADR_TYPE_INTL = "INTL";
public static final String PARAM_LANGUAGE = "LANGUAGE";
// SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
public static final String PARAM_SORT_AS = "SORT-AS";
// TYPE parameters not officially valid but used in some vCard exporter. // TYPE parameters not officially valid but used in some vCard exporter.
// Do not use in composer side. // Do not use in composer side.
public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY"; public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
// vCard 3.0.
public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
public interface ImportOnly { public interface ImportOnly {
public static final String PROPERTY_X_NICKNAME = "X-NICKNAME"; public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk, // Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,7 +158,14 @@ public class VCardConstants {
public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK"; public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
} }
/* package */ static final int MAX_DATA_COLUMN = 15; //// Mainly for package constants.
// DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
// SORT-STRING invCard 3.0.
/* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";
// Used in unit test.
public static final int MAX_DATA_COLUMN = 15;
/* package */ static final int MAX_CHARACTER_NUMS_QP = 76; /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75; static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;

View File

@@ -20,13 +20,11 @@ import android.content.ContentProviderOperation;
import android.content.ContentProviderResult; import android.content.ContentProviderResult;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.OperationApplicationException; import android.content.OperationApplicationException;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.RemoteException; import android.os.RemoteException;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -61,9 +59,6 @@ public class VCardEntry {
private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK; private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK;
private static final String ACCOUNT_TYPE_GOOGLE = "com.google";
private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts";
private static final Map<String, Integer> sImMap = new HashMap<String, Integer>(); private static final Map<String, Integer> sImMap = new HashMap<String, Integer>();
static { static {
@@ -78,12 +73,12 @@ public class VCardEntry {
Im.PROTOCOL_GOOGLE_TALK); Im.PROTOCOL_GOOGLE_TALK);
} }
static public class PhoneData { public static class PhoneData {
public final int type; public final int type;
public final String data; public final String data;
public final String label; public final String label;
// isPrimary is changable only when there's no appropriate one existing in // isPrimary is (not final but) changable, only when there's no appropriate one existing
// the original VCard. // in the original VCard.
public boolean isPrimary; public boolean isPrimary;
public PhoneData(int type, String data, String label, boolean isPrimary) { public PhoneData(int type, String data, String label, boolean isPrimary) {
this.type = type; this.type = type;
@@ -109,13 +104,11 @@ public class VCardEntry {
} }
} }
static public class EmailData { public static class EmailData {
public final int type; public final int type;
public final String data; public final String data;
// Used only when TYPE is TYPE_CUSTOM. // Used only when TYPE is TYPE_CUSTOM.
public final String label; public final String label;
// isPrimary is changable only when there's no appropriate one existing in
// the original VCard.
public boolean isPrimary; public boolean isPrimary;
public EmailData(int type, String data, String label, boolean isPrimary) { public EmailData(int type, String data, String label, boolean isPrimary) {
this.type = type; this.type = type;
@@ -141,9 +134,9 @@ public class VCardEntry {
} }
} }
static public class PostalData { public static class PostalData {
// Determined by vCard spec. // Determined by vCard specification.
// PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
public static final int ADDR_MAX_DATA_SIZE = 7; public static final int ADDR_MAX_DATA_SIZE = 7;
private final String[] dataArray; private final String[] dataArray;
public final String pobox; public final String pobox;
@@ -248,24 +241,28 @@ public class VCardEntry {
} }
} }
static public class OrganizationData { public static class OrganizationData {
public final int type; public final int type;
// non-final is Intentional: we may change the values since this info is separated into // non-final is Intentional: we may change the values since this info is separated into
// two parts in vCard: "ORG" + "TITLE". // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in
// different timing.
public String companyName; public String companyName;
public String departmentName; public String departmentName;
public String titleName; public String titleName;
public final String phoneticName; // We won't have this in "TITLE" property.
public boolean isPrimary; public boolean isPrimary;
public OrganizationData(int type, public OrganizationData(int type,
String companyName, final String companyName,
String departmentName, final String departmentName,
String titleName, final String titleName,
boolean isPrimary) { final String phoneticName,
final boolean isPrimary) {
this.type = type; this.type = type;
this.companyName = companyName; this.companyName = companyName;
this.departmentName = departmentName; this.departmentName = departmentName;
this.titleName = titleName; this.titleName = titleName;
this.phoneticName = phoneticName;
this.isPrimary = isPrimary; this.isPrimary = isPrimary;
} }
@@ -313,7 +310,7 @@ public class VCardEntry {
} }
} }
static public class ImData { public static class ImData {
public final int protocol; public final int protocol;
public final String customProtocol; public final String customProtocol;
public final int type; public final int type;
@@ -434,6 +431,14 @@ public class VCardEntry {
} }
} }
// TODO(dmiyakawa): 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
private String mFamilyName; private String mFamilyName;
private String mGivenName; private String mGivenName;
private String mMiddleName; private String mMiddleName;
@@ -441,7 +446,7 @@ public class VCardEntry {
private String mSuffix; private String mSuffix;
// Used only when no family nor given name is found. // Used only when no family nor given name is found.
private String mFullName; private String mFormattedName;
private String mPhoneticFamilyName; private String mPhoneticFamilyName;
private String mPhoneticGivenName; private String mPhoneticGivenName;
@@ -454,6 +459,7 @@ public class VCardEntry {
private String mDisplayName; private String mDisplayName;
private String mBirthday; private String mBirthday;
private String mAnniversary;
private List<String> mNoteList; private List<String> mNoteList;
private List<PhoneData> mPhoneList; private List<PhoneData> mPhoneList;
@@ -469,7 +475,7 @@ public class VCardEntry {
private final Account mAccount; private final Account mAccount;
public VCardEntry() { public VCardEntry() {
this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); this(VCardConfig.VCARD_TYPE_V21_GENERIC);
} }
public VCardEntry(int vcardType) { public VCardEntry(int vcardType) {
@@ -499,7 +505,6 @@ public class VCardEntry {
} }
} }
// Use NANP in default when there's no information about locale.
final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType); final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType); formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
} }
@@ -529,22 +534,44 @@ public class VCardEntry {
} }
/** /**
* Should be called via {@link #handleOrgValue(int, List, boolean)} or * Should be called via {@link #handleOrgValue(int, List, Map, boolean) or
* {@link #handleTitleValue(String)}. * {@link #handleTitleValue(String)}.
*/ */
private void addNewOrganization(int type, final String companyName, private void addNewOrganization(int type, final String companyName,
final String departmentName, final String departmentName,
final String titleName, boolean isPrimary) { final String titleName,
final String phoneticName,
final boolean isPrimary) {
if (mOrganizationList == null) { if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>(); mOrganizationList = new ArrayList<OrganizationData>();
} }
mOrganizationList.add(new OrganizationData(type, companyName, mOrganizationList.add(new OrganizationData(type, companyName,
departmentName, titleName, isPrimary)); departmentName, titleName, phoneticName, isPrimary));
} }
private static final List<String> sEmptyList = private static final List<String> sEmptyList =
Collections.unmodifiableList(new ArrayList<String>(0)); 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 * 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 * {@link OrganizationData} objects, this input data are attached to the last one which
@@ -552,7 +579,9 @@ public class VCardEntry {
* {@link OrganizationData} object, a new {@link OrganizationData} is created, * {@link OrganizationData} object, a new {@link OrganizationData} is created,
* whose title is set to null. * whose title is set to null.
*/ */
private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) { private void handleOrgValue(final int type, List<String> orgList,
Map<String, Collection<String>> paramMap, boolean isPrimary) {
final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap);
if (orgList == null) { if (orgList == null) {
orgList = sEmptyList; orgList = sEmptyList;
} }
@@ -587,7 +616,7 @@ public class VCardEntry {
if (mOrganizationList == null) { if (mOrganizationList == null) {
// Create new first organization entry, with "null" title which may be // Create new first organization entry, with "null" title which may be
// added via handleTitleValue(). // added via handleTitleValue().
addNewOrganization(type, companyName, departmentName, null, isPrimary); addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
return; return;
} }
for (OrganizationData organizationData : mOrganizationList) { for (OrganizationData organizationData : mOrganizationList) {
@@ -605,7 +634,7 @@ public class VCardEntry {
} }
// No OrganizatioData is available. Create another one, with "null" title, which may be // No OrganizatioData is available. Create another one, with "null" title, which may be
// added via handleTitleValue(). // added via handleTitleValue().
addNewOrganization(type, companyName, departmentName, null, isPrimary); addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
} }
/** /**
@@ -619,7 +648,7 @@ public class VCardEntry {
if (mOrganizationList == null) { if (mOrganizationList == null) {
// Create new first organization entry, with "null" other info, which may be // Create new first organization entry, with "null" other info, which may be
// added via handleOrgValue(). // added via handleOrgValue().
addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
return; return;
} }
for (OrganizationData organizationData : mOrganizationList) { for (OrganizationData organizationData : mOrganizationList) {
@@ -630,7 +659,7 @@ public class VCardEntry {
} }
// No Organization is available. Create another one, with "null" other info, which may be // No Organization is available. Create another one, with "null" other info, which may be
// added via handleOrgValue(). // added via handleOrgValue().
addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
} }
private void addIm(int protocol, String customProtocol, int type, private void addIm(int protocol, String customProtocol, int type,
@@ -656,11 +685,54 @@ public class VCardEntry {
mPhotoList.add(photoData); 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(mPhoneticFamilyName) &&
TextUtils.isEmpty(mPhoneticMiddleName) &&
TextUtils.isEmpty(mPhoneticGivenName))) {
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: mPhoneticMiddleName = sortNames.get(2); //$FALL-THROUGH$
case 2: mPhoneticGivenName = sortNames.get(1); //$FALL-THROUGH$
default: mPhoneticFamilyName = sortNames.get(0); break;
}
}
}
@SuppressWarnings("fallthrough") @SuppressWarnings("fallthrough")
private void handleNProperty(List<String> elems) { 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) // Family, Given, Middle, Prefix, Suffix. (1 - 5)
int size; int size;
if (elems == null || (size = elems.size()) < 1) { if (paramValues == null || (size = paramValues.size()) < 1) {
return; return;
} }
if (size > 5) { if (size > 5) {
@@ -668,12 +740,12 @@ public class VCardEntry {
} }
switch (size) { switch (size) {
// fallthrough // Fall-through.
case 5: mSuffix = elems.get(4); case 5: mSuffix = paramValues.get(4);
case 4: mPrefix = elems.get(3); case 4: mPrefix = paramValues.get(3);
case 3: mMiddleName = elems.get(2); case 3: mMiddleName = paramValues.get(2);
case 2: mGivenName = elems.get(1); case 2: mGivenName = paramValues.get(1);
default: mFamilyName = elems.get(0); default: mFamilyName = paramValues.get(0);
} }
} }
@@ -754,13 +826,13 @@ public class VCardEntry {
if (propName.equals(VCardConstants.PROPERTY_VERSION)) { if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this. // vCard version. Ignore this.
} else if (propName.equals(VCardConstants.PROPERTY_FN)) { } else if (propName.equals(VCardConstants.PROPERTY_FN)) {
mFullName = propValue; mFormattedName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) { } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) {
// Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not
// actually exist in the real vCard data, does not exist. // actually exist in the real vCard data, does not exist.
mFullName = propValue; mFormattedName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_N)) { } else if (propName.equals(VCardConstants.PROPERTY_N)) {
handleNProperty(propValueList); handleNProperty(propValueList, paramMap);
} else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) { } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue; mPhoneticFullName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) || } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
@@ -775,8 +847,7 @@ public class VCardEntry {
// which is correct behavior from the view of vCard 2.1. // which is correct behavior from the view of vCard 2.1.
// But we want it to be separated, so do the separation here. // But we want it to be separated, so do the separation here.
final List<String> phoneticNameList = final List<String> phoneticNameList =
VCardUtils.constructListFromValue(propValue, VCardUtils.constructListFromValue(propValue, mVCardType);
VCardConfig.isV30(mVCardType));
handlePhoneticNameFromSound(phoneticNameList); handlePhoneticNameFromSound(phoneticNameList);
} else { } else {
// Ignore this field since Android cannot understand what it is. // Ignore this field since Android cannot understand what it is.
@@ -877,7 +948,7 @@ public class VCardEntry {
} }
} }
} }
handleOrgValue(type, propValueList, isPrimary); handleOrgValue(type, propValueList, paramMap, isPrimary);
} else if (propName.equals(VCardConstants.PROPERTY_TITLE)) { } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
handleTitleValue(propValue); handleTitleValue(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_ROLE)) { } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
@@ -966,6 +1037,8 @@ public class VCardEntry {
mWebsiteList.add(propValue); mWebsiteList.add(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_BDAY)) { } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
mBirthday = propValue; mBirthday = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) {
mAnniversary = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) { } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue; mPhoneticGivenName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) { } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
@@ -974,33 +1047,9 @@ public class VCardEntry {
mPhoneticFamilyName = propValue; mPhoneticFamilyName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) { } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
final List<String> customPropertyList = final List<String> customPropertyList =
VCardUtils.constructListFromValue(propValue, VCardUtils.constructListFromValue(propValue, mVCardType);
VCardConfig.isV30(mVCardType));
handleAndroidCustomProperty(customPropertyList); handleAndroidCustomProperty(customPropertyList);
/*} else if (propName.equals("REV")) {
// Revision of this VCard entry. I think we can ignore this.
} else if (propName.equals("UID")) {
} else if (propName.equals("KEY")) {
// Type is X509 or PGP? I don't know how to handle this...
} else if (propName.equals("MAILER")) {
} else if (propName.equals("TZ")) {
} else if (propName.equals("GEO")) {
} else if (propName.equals("CLASS")) {
// vCard 3.0 only.
// e.g. CLASS:CONFIDENTIAL
} else if (propName.equals("PROFILE")) {
// VCard 3.0 only. Must be "VCARD". I think we can ignore this.
} else if (propName.equals("CATEGORIES")) {
// VCard 3.0 only.
// e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY
} else if (propName.equals("SOURCE")) {
// VCard 3.0 only.
} else if (propName.equals("PRODID")) {
// VCard 3.0 only.
// To specify the identifier for the product that created
// the vCard object.*/
} else { } else {
// Unknown X- words and IANA token.
} }
} }
@@ -1016,8 +1065,8 @@ public class VCardEntry {
*/ */
private void constructDisplayName() { private void constructDisplayName() {
// FullName (created via "FN" or "NAME" field) is prefered. // FullName (created via "FN" or "NAME" field) is prefered.
if (!TextUtils.isEmpty(mFullName)) { if (!TextUtils.isEmpty(mFormattedName)) {
mDisplayName = mFullName; mDisplayName = mFormattedName;
} else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) { } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType, mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix); mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
@@ -1062,23 +1111,6 @@ public class VCardEntry {
if (mAccount != null) { if (mAccount != null) {
builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name); builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type); builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type);
// Assume that caller side creates this group if it does not exist.
if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) {
final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] {
Groups.SOURCE_ID },
Groups.TITLE + "=?", new String[] {
GOOGLE_MY_CONTACTS_GROUP }, null);
try {
if (cursor != null && cursor.moveToFirst()) {
myGroupsId = cursor.getString(0);
}
} finally {
if (cursor != null) {
cursor.close();
}
}
}
} else { } else {
builder.withValue(RawContacts.ACCOUNT_NAME, null); builder.withValue(RawContacts.ACCOUNT_NAME, null);
builder.withValue(RawContacts.ACCOUNT_TYPE, null); builder.withValue(RawContacts.ACCOUNT_TYPE, null);
@@ -1154,6 +1186,9 @@ public class VCardEntry {
if (organizationData.titleName != null) { if (organizationData.titleName != null) {
builder.withValue(Organization.TITLE, organizationData.titleName); builder.withValue(Organization.TITLE, organizationData.titleName);
} }
if (organizationData.phoneticName != null) {
builder.withValue(Organization.PHONETIC_NAME, organizationData.phoneticName);
}
if (organizationData.isPrimary) { if (organizationData.isPrimary) {
builder.withValue(Organization.IS_PRIMARY, 1); builder.withValue(Organization.IS_PRIMARY, 1);
} }
@@ -1251,6 +1286,15 @@ public class VCardEntry {
operationList.add(builder.build()); operationList.add(builder.build());
} }
if (!TextUtils.isEmpty(mAnniversary)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Event.RAW_CONTACT_ID, 0);
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());
}
if (mAndroidCustomPropertyList != null) { if (mAndroidCustomPropertyList != null) {
for (List<String> customPropertyList : mAndroidCustomPropertyList) { for (List<String> customPropertyList : mAndroidCustomPropertyList) {
int size = customPropertyList.size(); int size = customPropertyList.size();
@@ -1322,7 +1366,7 @@ public class VCardEntry {
&& TextUtils.isEmpty(mGivenName) && TextUtils.isEmpty(mGivenName)
&& TextUtils.isEmpty(mPrefix) && TextUtils.isEmpty(mPrefix)
&& TextUtils.isEmpty(mSuffix) && TextUtils.isEmpty(mSuffix)
&& TextUtils.isEmpty(mFullName) && TextUtils.isEmpty(mFormattedName)
&& TextUtils.isEmpty(mPhoneticFamilyName) && TextUtils.isEmpty(mPhoneticFamilyName)
&& TextUtils.isEmpty(mPhoneticMiddleName) && TextUtils.isEmpty(mPhoneticMiddleName)
&& TextUtils.isEmpty(mPhoneticGivenName) && TextUtils.isEmpty(mPhoneticGivenName)
@@ -1381,7 +1425,7 @@ public class VCardEntry {
} }
public String getFullName() { public String getFullName() {
return mFullName; return mFormattedName;
} }
public String getPhoneticFamilyName() { public String getPhoneticFamilyName() {

View File

@@ -52,9 +52,9 @@ public class VCardEntryCommitter implements VCardEntryHandler {
} }
} }
public void onEntryCreated(final VCardEntry contactStruct) { public void onEntryCreated(final VCardEntry vcardEntry) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver)); mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
mTimeToCommit += System.currentTimeMillis() - start; mTimeToCommit += System.currentTimeMillis() - start;
} }

View File

@@ -16,78 +16,82 @@
package android.pim.vcard; package android.pim.vcard;
import android.accounts.Account; import android.accounts.Account;
import android.text.TextUtils;
import android.util.Base64;
import android.util.CharsetUtils; import android.util.CharsetUtils;
import android.util.Log; import android.util.Log;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.net.QuotedPrintableCodec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
/**
* <p>
* The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects
* to easily handle each vCard entry.
* </p>
* <p>
* This class understand details inside vCard and translates it to {@link VCardEntry}.
* Then the class throw it to {@link VCardEntryHandler} registered via
* {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects
* are able to handle the {@link VCardEntry} object.
* </p>
* <p>
* If you want to know the detail inside vCard, it would be better to implement
* {@link VCardInterpreter} directly, instead of relying on this class and
* {@link VCardEntry} created by the object.
* </p>
*/
public class VCardEntryConstructor implements VCardInterpreter { public class VCardEntryConstructor implements VCardInterpreter {
private static String LOG_TAG = "VCardEntryConstructor"; private static String LOG_TAG = "VCardEntryConstructor";
/**
* If there's no other information available, this class uses this charset for encoding
* byte arrays to String.
*/
/* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8";
private VCardEntry.Property mCurrentProperty = new VCardEntry.Property(); private VCardEntry.Property mCurrentProperty = new VCardEntry.Property();
private VCardEntry mCurrentContactStruct; private VCardEntry mCurrentVCardEntry;
private String mParamType; private String mParamType;
/** // The charset using which {@link VCardInterpreter} parses the text.
* The charset using which {@link VCardInterpreter} parses the text. // Each String is first decoded into binary stream with this charset, and encoded back
*/ // to "target charset", which may be explicitly specified by the vCard with "CHARSET"
private String mInputCharset; // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
private final String mSourceCharset;
/** private final boolean mStrictLineBreaking;
* The charset with which byte array is encoded to String. private final int mVCardType;
*/ private final Account mAccount;
final private String mCharsetForDecodedBytes;
final private boolean mStrictLineBreakParsing;
final private int mVCardType;
final private Account mAccount;
/** For measuring performance. */ // For measuring performance.
private long mTimePushIntoContentResolver; private long mTimePushIntoContentResolver;
final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>(); private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
public VCardEntryConstructor() { public VCardEntryConstructor() {
this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null); this(VCardConfig.VCARD_TYPE_V21_GENERIC, null);
} }
public VCardEntryConstructor(final int vcardType) { public VCardEntryConstructor(final int vcardType) {
this(null, null, false, vcardType, null); this(vcardType, null, null, false);
} }
public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing, public VCardEntryConstructor(final int vcardType, final Account account) {
final int vcardType, final Account account) { this(vcardType, account, null, false);
this(null, charset, strictLineBreakParsing, vcardType, account);
} }
public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes, public VCardEntryConstructor(final int vcardType, final Account account,
final boolean strictLineBreakParsing, final int vcardType, final String inputCharset) {
final Account account) { this(vcardType, account, inputCharset, false);
}
/**
* @hide Just for testing.
*/
public VCardEntryConstructor(final int vcardType, final Account account,
final String inputCharset, final boolean strictLineBreakParsing) {
if (inputCharset != null) { if (inputCharset != null) {
mInputCharset = inputCharset; mSourceCharset = inputCharset;
} else { } else {
mInputCharset = VCardConfig.DEFAULT_CHARSET; mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
} }
if (charsetForDetodedBytes != null) { mStrictLineBreaking = strictLineBreakParsing;
mCharsetForDecodedBytes = charsetForDetodedBytes;
} else {
mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
}
mStrictLineBreakParsing = strictLineBreakParsing;
mVCardType = vcardType; mVCardType = vcardType;
mAccount = account; mAccount = account;
} }
@@ -95,60 +99,63 @@ public class VCardEntryConstructor implements VCardInterpreter {
public void addEntryHandler(VCardEntryHandler entryHandler) { public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler); mEntryHandlers.add(entryHandler);
} }
@Override
public void start() { public void start() {
for (VCardEntryHandler entryHandler : mEntryHandlers) { for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onStart(); entryHandler.onStart();
} }
} }
@Override
public void end() { public void end() {
for (VCardEntryHandler entryHandler : mEntryHandlers) { for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEnd(); entryHandler.onEnd();
} }
} }
/**
* Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
*/
public void clear() { public void clear() {
mCurrentContactStruct = null; mCurrentVCardEntry = null;
mCurrentProperty = new VCardEntry.Property(); mCurrentProperty = new VCardEntry.Property();
} }
/** @Override
* Assume that VCard is not nested. In other words, this code does not accept
*/
public void startEntry() { public void startEntry() {
if (mCurrentContactStruct != null) { if (mCurrentVCardEntry != null) {
Log.e(LOG_TAG, "Nested VCard code is not supported now."); Log.e(LOG_TAG, "Nested VCard code is not supported now.");
} }
mCurrentContactStruct = new VCardEntry(mVCardType, mAccount); mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount);
} }
@Override
public void endEntry() { public void endEntry() {
mCurrentContactStruct.consolidateFields(); mCurrentVCardEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) { for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEntryCreated(mCurrentContactStruct); entryHandler.onEntryCreated(mCurrentVCardEntry);
} }
mCurrentContactStruct = null; mCurrentVCardEntry = null;
} }
@Override
public void startProperty() { public void startProperty() {
mCurrentProperty.clear(); mCurrentProperty.clear();
} }
@Override
public void endProperty() { public void endProperty() {
mCurrentContactStruct.addProperty(mCurrentProperty); mCurrentVCardEntry.addProperty(mCurrentProperty);
} }
@Override
public void propertyName(String name) { public void propertyName(String name) {
mCurrentProperty.setPropertyName(name); mCurrentProperty.setPropertyName(name);
} }
@Override
public void propertyGroup(String group) { public void propertyGroup(String group) {
} }
@Override
public void propertyParamType(String type) { public void propertyParamType(String type) {
if (mParamType != null) { if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " + Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -164,119 +171,34 @@ public class VCardEntryConstructor implements VCardInterpreter {
mParamType = "TYPE"; mParamType = "TYPE";
} }
if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
value = encodeString(value, mCharsetForDecodedBytes); value = VCardUtils.convertStringCharset(
value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
} }
mCurrentProperty.addParameter(mParamType, value); mCurrentProperty.addParameter(mParamType, value);
mParamType = null; mParamType = null;
} }
private String encodeString(String originalString, String charsetForDecodedBytes) { private String handleOneValue(String value,
if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) { String sourceCharset, String targetCharset, String encoding) {
return originalString; // It is possible when some of multiple values are empty.
// e.g. N:;a;;; -> values are "", "a", "", "", and "".
if (TextUtils.isEmpty(value)) {
return "";
} }
Charset charset = Charset.forName(mInputCharset);
ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return null;
}
}
private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) {
if (encoding != null) { if (encoding != null) {
if (encoding.equals("BASE64") || encoding.equals("B")) { if (encoding.equals("BASE64") || encoding.equals("B")) {
mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes())); mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
return value; return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) { } else if (encoding.equals("QUOTED-PRINTABLE")) {
// "= " -> " ", "=\t" -> "\t". return VCardUtils.parseQuotedPrintable(
// Previous code had done this replacement. Keep on the safe side. value, mStrictLineBreaking, sourceCharset, targetCharset);
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);
if (nextCh == ' ' || nextCh == '\t') {
builder.append(nextCh);
i++;
continue;
}
}
builder.append(ch);
}
String quotedPrintable = builder.toString();
String[] lines;
if (mStrictLineBreakParsing) {
lines = quotedPrintable.split("\r\n");
} else {
builder = new StringBuilder();
length = quotedPrintable.length();
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < length; i++) {
char ch = quotedPrintable.charAt(i);
if (ch == '\n') {
list.add(builder.toString());
builder = new StringBuilder();
} else if (ch == '\r') {
list.add(builder.toString());
builder = new StringBuilder();
if (i < length - 1) {
char nextCh = quotedPrintable.charAt(i + 1);
if (nextCh == '\n') {
i++;
}
}
} else {
builder.append(ch);
}
}
String finalLine = builder.toString();
if (finalLine.length() > 0) {
list.add(finalLine);
}
lines = list.toArray(new String[0]);
}
builder = new StringBuilder();
for (String line : lines) {
if (line.endsWith("=")) {
line = line.substring(0, line.length() - 1);
}
builder.append(line);
}
byte[] bytes;
try {
bytes = builder.toString().getBytes(mInputCharset);
} catch (UnsupportedEncodingException e1) {
Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset);
bytes = builder.toString().getBytes();
}
try {
bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
} catch (DecoderException e) {
Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
return "";
}
try {
return new String(bytes, charsetForDecodedBytes);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes);
return new String(bytes);
}
} }
// Unknown encoding. Fall back to default. Log.w(LOG_TAG, "Unknown encoding. Fall back to default.");
} }
return encodeString(value, charsetForDecodedBytes);
// Just translate the charset of a given String from inputCharset to a system one.
return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset);
} }
public void propertyValues(List<String> values) { public void propertyValues(List<String> values) {
@@ -284,24 +206,27 @@ public class VCardEntryConstructor implements VCardInterpreter {
return; return;
} }
final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET"); final Collection<String> charsetCollection =
final String charset = mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
((charsetCollection != null) ? charsetCollection.iterator().next() : null); final Collection<String> encodingCollection =
final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING"); mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
final String encoding = final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null); ((encodingCollection != null) ? encodingCollection.iterator().next() : null);
String targetCharset = CharsetUtils.nameForDefaultVendor(
String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset); ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) { if (TextUtils.isEmpty(targetCharset)) {
charsetForDecodedBytes = mCharsetForDecodedBytes; targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
} }
for (final String value : values) { for (final String value : values) {
mCurrentProperty.addToPropertyValueList( mCurrentProperty.addToPropertyValueList(
handleOneValue(value, charsetForDecodedBytes, encoding)); handleOneValue(value, mSourceCharset, targetCharset, encoding));
} }
} }
/**
* @hide
*/
public void showPerformanceInfo() { public void showPerformanceInfo() {
Log.d(LOG_TAG, "time for insert ContactStruct to database: " + Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
mTimePushIntoContentResolver + " ms"); mTimePushIntoContentResolver + " ms");

View File

@@ -16,8 +16,13 @@
package android.pim.vcard; package android.pim.vcard;
/** /**
* The interface called by {@link VCardEntryConstructor}. Useful when you don't want to * <p>
* handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}. * The interface called by {@link VCardEntryConstructor}.
* </p>
* <p>
* This class is useful when you don't want to know vCard data in detail. If you want to know
* it, it would be better to consider using {@link VCardInterpreter}.
* </p>
*/ */
public interface VCardEntryHandler { public interface VCardEntryHandler {
/** /**

View File

@@ -20,7 +20,7 @@ import java.util.List;
/** /**
* <P> * <P>
* The interface which should be implemented by the classes which have to analyze each * The interface which should be implemented by the classes which have to analyze each
* vCard entry more minutely than {@link VCardEntry} class analysis. * vCard entry minutely.
* </P> * </P>
* <P> * <P>
* Here, there are several terms specific to vCard (and this library). * Here, there are several terms specific to vCard (and this library).

View File

@@ -23,9 +23,9 @@ import java.util.List;
* {@link VCardInterpreter} objects and make a user object treat them as one * {@link VCardInterpreter} objects and make a user object treat them as one
* {@link VCardInterpreter} object. * {@link VCardInterpreter} object.
*/ */
public class VCardInterpreterCollection implements VCardInterpreter { public final class VCardInterpreterCollection implements VCardInterpreter {
private final Collection<VCardInterpreter> mInterpreterCollection; private final Collection<VCardInterpreter> mInterpreterCollection;
public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) { public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
mInterpreterCollection = interpreterCollection; mInterpreterCollection = interpreterCollection;
} }

View File

@@ -20,82 +20,36 @@ import android.pim.vcard.exception.VCardException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
public abstract class VCardParser { public interface VCardParser {
protected final int mParseType;
protected boolean mCanceled;
public VCardParser() {
this(VCardConfig.PARSE_TYPE_UNKNOWN);
}
public VCardParser(int parseType) {
mParseType = parseType;
}
/** /**
* <P> * <p>
* Parses the given stream and send the VCard data into VCardBuilderBase object. * Parses the given stream and send the vCard data into VCardBuilderBase object.
* </P. * </p>.
* <P> * <p>
* Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets
* local encoding to it. For example, Japanese phone career uses Shift_JIS, which is * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is
* formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1, * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
* In some exreme case, some VCard may have different charsets in one VCard (though * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
* we do not see any device which emits such kind of malicious data) * </p>
* </P> * <p>
* <P> * We recommend you use {@link VCardSourceDetector} and detect which kind of source the
* In order to avoid "misunderstanding" charset as much as possible, this method * vCard comes from and explicitly specify a charset using the result.
* use "ISO-8859-1" for reading the stream. When charset is specified in some property * </p>
* (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to
* the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit
* characters, which is not completely sure. In some cases, this "decoding-encoding"
* scheme may fail. To avoid the case,
* </P>
* <P>
* We recommend you to use {@link VCardSourceDetector} and detect which kind of source the
* VCard comes from and explicitly specify a charset using the result.
* </P>
* *
* @param is The source to parse. * @param is The source to parse.
* @param interepreter A {@link VCardInterpreter} object which used to construct data. * @param interepreter A {@link VCardInterpreter} object which used to construct data.
* @return Returns true for success. Otherwise returns false.
* @throws IOException, VCardException * @throws IOException, VCardException
*/ */
public abstract boolean parse(InputStream is, VCardInterpreter interepreter) public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException; throws IOException, VCardException;
/** /**
* <P> * <p>
* The method variants which accept charset. * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
* </P> * </p>
* <P> * <p>
* RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use * Actual cancel is done after parsing the current vcard.
* UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese * </p>
* phone uses Shift_JIS as a charset (e.g. W61SH), and another uses
* "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K).
* </P>
*
* @param is The source to parse.
* @param charset Charset to be used.
* @param builder The VCardBuilderBase object.
* @return Returns true when successful. Otherwise returns false.
* @throws IOException, VCardException
*/ */
public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder) public abstract void cancel();
throws IOException, VCardException;
/**
* The method variants which tells this object the operation is already canceled.
*/
public abstract void parse(InputStream is, String charset,
VCardInterpreter builder, boolean canceled)
throws IOException, VCardException;
/**
* Cancel parsing.
* Actual cancel is done after the end of the current one vcard entry parsing.
*/
public void cancel() {
mCanceled = true;
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,388 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
import android.util.Log;
import java.io.IOException;
import java.util.Set;
/**
* <p>
* Basic implementation achieving vCard 3.0 parsing.
* </p>
* <p>
* This class inherits vCard 2.1 implementation since technically they are similar,
* while specifically there's logical no relevance between them.
* So that developers are not confused with the inheritance,
* {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while
* {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}.
* </p>
* @hide
*/
/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 {
private static final String LOG_TAG = "VCardParserImpl_V30";
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
public VCardParserImpl_V30() {
super();
}
public VCardParserImpl_V30(int vcardType) {
super(vcardType);
}
@Override
protected int getVersion() {
return VCardConfig.VERSION_30;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V30;
}
@Override
protected String getLine() throws IOException {
if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
} else {
return mReader.readLine();
}
}
/**
* vCard 3.0 requires that the line with space at the beginning of the line
* must be combined with previous line.
*/
@Override
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
StringBuilder builder = null;
while (true) {
line = mReader.readLine();
if (line == null) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
throw new VCardException("Reached end of buffer.");
} else if (line.length() == 0) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
if (builder != null) {
// See Section 5.8.1 of RFC 2425 (MIME-DIR document).
// Following is the excerpts from it.
//
// DESCRIPTION:This is a long description that exists on a long line.
//
// Can be represented as:
//
// DESCRIPTION:This is a long description
// that exists on a long line.
//
// It could also be represented as:
//
// DESCRIPTION:This is a long descrip
// tion that exists o
// n a long line.
builder.append(line.substring(1));
} else if (mPreviousLine != null) {
builder = new StringBuilder();
builder.append(mPreviousLine);
mPreviousLine = null;
builder.append(line.substring(1));
} else {
throw new VCardException("Space exists at the beginning of the line");
}
} else {
if (mPreviousLine == null) {
mPreviousLine = line;
if (builder != null) {
return builder.toString();
}
} else {
String ret = mPreviousLine;
mPreviousLine = line;
return ret;
}
}
}
}
/*
* vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
* 1 * (contentline)
* ;A vCard object MUST include the VERSION, FN and N types.
* [group "."] "END" ":" "VCARD" 1 * CRLF
*/
@Override
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
return super.readBeginVCard(allowGarbage);
}
@Override
protected void readEndVCard(boolean useCache, boolean allowGarbage)
throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
super.readEndVCard(useCache, allowGarbage);
}
/**
* vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
*/
@Override
protected void handleParams(final String params) throws VCardException {
try {
super.handleParams(params);
} catch (VCardException e) {
// maybe IANA type
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
handleAnyParam(strArray[0], strArray[1]);
} else {
// Must not come here in the current implementation.
throw new VCardException(
"Unknown params value: " + params);
}
}
}
@Override
protected void handleAnyParam(final String paramName, final String paramValue) {
mInterpreter.propertyParamType(paramName);
splitAndPutParamValue(paramValue);
}
@Override
protected void handleParamWithoutName(final String paramValue) {
handleType(paramValue);
}
/*
* vCard 3.0 defines
*
* param = param-name "=" param-value *("," param-value)
* param-name = iana-token / x-name
* param-value = ptext / quoted-string
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
* ; Any character except CTLs, DQUOTE
*
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\").
*/
@Override
protected void handleType(final String paramValue) {
mInterpreter.propertyParamType("TYPE");
splitAndPutParamValue(paramValue);
}
/**
* Splits parameter values into pieces in accordance with vCard 3.0 specification and
* puts pieces into mInterpreter.
*/
/*
* param-value = ptext / quoted-string
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
* QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII
* ; Any character except CTLs, DQUOTE
*
* QSAFE-CHAR must not contain DQUOTE, including escaped one (\")
*/
private void splitAndPutParamValue(String paramValue) {
// "comma,separated:inside.dquote",pref
// -->
// - comma,separated:inside.dquote
// - pref
//
// Note: Though there's a code, we don't need to take much care of
// wrongly-added quotes like the example above, as they induce
// parse errors at the top level (when splitting a line into parts).
StringBuilder builder = null; // Delay initialization.
boolean insideDquote = false;
final int length = paramValue.length();
for (int i = 0; i < length; i++) {
final char ch = paramValue.charAt(i);
if (ch == '"') {
if (insideDquote) {
// End of Dquote.
mInterpreter.propertyParamValue(builder.toString());
builder = null;
insideDquote = false;
} else {
if (builder != null) {
if (builder.length() > 0) {
// e.g.
// pref"quoted"
Log.w(LOG_TAG, "Unexpected Dquote inside property.");
} else {
// e.g.
// pref,"quoted"
// "quoted",pref
mInterpreter.propertyParamValue(builder.toString());
}
}
insideDquote = true;
}
} else if (ch == ',' && !insideDquote) {
if (builder == null) {
Log.w(LOG_TAG, "Comma is used before actual string comes. (" +
paramValue + ")");
} else {
mInterpreter.propertyParamValue(builder.toString());
builder = null;
}
} else {
// To stop creating empty StringBuffer at the end of parameter,
// we delay creating this object until this point.
if (builder == null) {
builder = new StringBuilder();
}
builder.append(ch);
}
}
if (insideDquote) {
// e.g.
// "non-quote-at-end
Log.d(LOG_TAG, "Dangling Dquote.");
}
if (builder != null) {
if (builder.length() == 0) {
Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " +
"at the end of parameter value parsing.");
} else {
mInterpreter.propertyParamValue(builder.toString());
}
}
}
@Override
protected void handleAgent(final String propertyValue) {
// The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
//
// e.g.
// AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
// TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
// ET:jfriday@host.com\nEND:VCARD\n
//
// TODO: fix this.
//
// issue:
// vCard 3.0 also allows this as an example.
//
// AGENT;VALUE=uri:
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
//
// This is not vCard. Should we support this?
//
// Just ignore the line for now, since we cannot know how to handle it...
if (!mEmittedAgentWarning) {
Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
mEmittedAgentWarning = true;
}
}
/**
* vCard 3.0 does not require two CRLF at the last of BASE64 data.
* It only requires that data should be MIME-encoded.
*/
@Override
protected String getBase64(final String firstString)
throws IOException, VCardException {
final StringBuilder builder = new StringBuilder();
builder.append(firstString);
while (true) {
final String line = getLine();
if (line == null) {
throw new VCardException("File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;
} else if (!line.startsWith(" ") && !line.startsWith("\t")) {
mPreviousLine = line;
break;
}
builder.append(line);
}
return builder.toString();
}
/**
* ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
*
* Note: Apple escapes ':' into '\:' while does not escape '\'
*/
@Override
protected String maybeUnescapeText(final String text) {
return unescapeText(text);
}
public static String unescapeText(final String text) {
StringBuilder builder = new StringBuilder();
final int length = text.length();
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
if (ch == '\\' && i < length - 1) {
final char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
builder.append("\n");
} else {
builder.append(next_ch);
}
} else {
builder.append(ch);
}
}
return builder.toString();
}
@Override
protected String maybeUnescapeCharacter(final char ch) {
return unescapeCharacter(ch);
}
public static String unescapeCharacter(final char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
}
}
@Override
protected Set<String> getKnownPropertyNameSet() {
return VCardParser_V30.sKnownPropertyNameSet;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import java.util.Set;
/**
* <p>
* Basic implementation parsing vCard 4.0.
* </p>
* <p>
* vCard 4.0 is not published yet. Also this implementation is premature.
* </p>
* @hide
*/
/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 {
// private static final String LOG_TAG = "VCardParserImpl_V40";
public VCardParserImpl_V40() {
super();
}
public VCardParserImpl_V40(final int vcardType) {
super(vcardType);
}
@Override
protected int getVersion() {
return VCardConfig.VERSION_40;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V40;
}
/**
* We escape "\N" into new line for safety.
*/
@Override
protected String maybeUnescapeText(final String text) {
return unescapeText(text);
}
public static String unescapeText(final String text) {
// TODO: more strictly, vCard 4.0 requires different type of unescaping rule
// toward each property.
final StringBuilder builder = new StringBuilder();
final int length = text.length();
for (int i = 0; i < length; i++) {
char ch = text.charAt(i);
if (ch == '\\' && i < length - 1) {
final char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
builder.append("\n");
} else {
builder.append(next_ch);
}
} else {
builder.append(ch);
}
}
return builder.toString();
}
public static String unescapeCharacter(final char ch) {
if (ch == 'n' || ch == 'N') {
return "\n";
} else {
return String.valueOf(ch);
}
}
@Override
protected Set<String> getKnownPropertyNameSet() {
return VCardParser_V40.sKnownPropertyNameSet;
}
}

View File

@@ -15,922 +15,95 @@
*/ */
package android.pim.vcard; package android.pim.vcard;
import android.pim.vcard.exception.VCardAgentNotSupportedException;
import android.pim.vcard.exception.VCardException; import android.pim.vcard.exception.VCardException;
import android.pim.vcard.exception.VCardInvalidCommentLineException;
import android.pim.vcard.exception.VCardInvalidLineException;
import android.pim.vcard.exception.VCardNestedException;
import android.pim.vcard.exception.VCardVersionException;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
/** /**
* This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail. * </p>
* vCard parser for vCard 2.1. See the specification for more detail about the spec itself.
* </p>
* <p>
* The spec is written in 1996, and currently various types of "vCard 2.1" exist.
* To handle real the world vCard formats appropriately and effectively, this class does not
* obey with strict vCard 2.1.
* In stead, not only vCard spec but also real world vCard is considered.
* </p>
* e.g. A lot of devices and softwares let vCard importer/exporter to use
* the PNG format to determine the type of image, while it is not allowed in
* the original specification. As of 2010, we can see even the FLV format
* (possible in Japanese mobile phones).
* </p>
*/ */
public class VCardParser_V21 extends VCardParser { public final class VCardParser_V21 implements VCardParser {
private static final String LOG_TAG = "VCardParser_V21"; /**
* A unmodifiable Set storing the property names available in the vCard 2.1 specification.
/** Store the known-type */ */
private static final HashSet<String> sKnownTypeSet = new HashSet<String>( /* package */ static final Set<String> sKnownPropertyNameSet =
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK", Collections.unmodifiableSet(new HashSet<String>(
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS", Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK", "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL", "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")));
"MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
"CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
"PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
"WAVE", "AIFF", "PCM", "X509", "PGP"));
/** Store the known-value */
private static final HashSet<String> sKnownValueSet = new HashSet<String>(
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
/** Store the property names available in vCard 2.1 */
private static final HashSet<String> sAvailablePropertyNameSetV21 =
new HashSet<String>(Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"));
/** /**
* A unmodifiable Set storing the types known in vCard 2.1.
*/
/* package */ static final Set<String> sKnownTypeSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK",
"PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS",
"MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK",
"ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL",
"MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF",
"CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF",
"PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI",
"WAVE", "AIFF", "PCM", "X509", "PGP")));
/**
* A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1.
*/
/* package */ static final Set<String> sKnownValueSet =
Collections.unmodifiableSet(new HashSet<String>(
Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")));
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "B" encoding, some data may have it. * Though vCard 2.1 specification does not allow "B" encoding, some data may have it.
* We allow it for safety... * We allow it for safety.
* </p>
*/ */
private static final HashSet<String> sAvailableEncodingV21 = /* package */ static final Set<String> sAvailableEncoding =
new HashSet<String>(Arrays.asList( Collections.unmodifiableSet(new HashSet<String>(
"7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B")); Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
// Used only for parsing END:VCARD. VCardConstants.PARAM_ENCODING_QP,
private String mPreviousLine; VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
/** The builder to build parsed data */
protected VCardInterpreter mBuilder = null;
/** private final VCardParserImpl_V21 mVCardParserImpl;
* The encoding type. "Encoding" in vCard is different from "Charset".
* e.g. 7BIT, 8BIT, QUOTED-PRINTABLE.
*/
protected String mEncoding = null;
protected final String sDefaultEncoding = "8BIT";
// Should not directly read a line from this object. Use getLine() instead.
protected BufferedReader mReader;
// In some cases, vCard is nested. Currently, we only consider the most interior vCard data.
// See v21_foma_1.vcf in test directory for more information.
private int mNestCount;
// In order to reduce warning message as much as possible, we hold the value which made Logger
// emit a warning message.
protected Set<String> mUnknownTypeMap = new HashSet<String>();
protected Set<String> mUnknownValueMap = new HashSet<String>();
// For measuring performance.
private long mTimeTotal;
private long mTimeReadStartRecord;
private long mTimeReadEndRecord;
private long mTimeStartProperty;
private long mTimeEndProperty;
private long mTimeParseItems;
private long mTimeParseLineAndHandleGroup;
private long mTimeParsePropertyValues;
private long mTimeParseAdrOrgN;
private long mTimeHandleMiscPropertyValue;
private long mTimeHandleQuotedPrintable;
private long mTimeHandleBase64;
public VCardParser_V21() { public VCardParser_V21() {
this(null); mVCardParserImpl = new VCardParserImpl_V21();
} }
public VCardParser_V21(VCardSourceDetector detector) { public VCardParser_V21(int vcardType) {
this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN); mVCardParserImpl = new VCardParserImpl_V21(vcardType);
} }
public VCardParser_V21(int parseType) { public void parse(InputStream is, VCardInterpreter interepreter)
super(parseType);
if (parseType == VCardConfig.PARSE_TYPE_FOMA) {
mNestCount = 1;
}
}
/**
* Parses the file at the given position.
*
* vcard_file = [wsls] vcard [wsls]
*/
protected void parseVCardFile() throws IOException, VCardException {
boolean firstReading = true;
while (true) {
if (mCanceled) {
break;
}
if (!parseOneVCard(firstReading)) {
break;
}
firstReading = false;
}
if (mNestCount > 0) {
boolean useCache = true;
for (int i = 0; i < mNestCount; i++) {
readEndVCard(useCache, true);
useCache = false;
}
}
}
protected int getVersion() {
return VCardConfig.FLAG_V21;
}
protected String getVersionString() {
return VCardConstants.VERSION_V21;
}
/**
* @return true when the propertyName is a valid property name.
*/
protected boolean isValidPropertyName(String propertyName) {
if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
propertyName.startsWith("X-")) &&
!mUnknownTypeMap.contains(propertyName)) {
mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
}
return true;
}
/**
* @return true when the encoding is a valid encoding.
*/
protected boolean isValidEncoding(String encoding) {
return sAvailableEncodingV21.contains(encoding.toUpperCase());
}
/**
* @return String. It may be null, or its length may be 0
* @throws IOException
*/
protected String getLine() throws IOException {
return mReader.readLine();
}
/**
* @return String with it's length > 0
* @throws IOException
* @throws VCardException when the stream reached end of line
*/
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
while (true) {
line = getLine();
if (line == null) {
throw new VCardException("Reached end of buffer.");
} else if (line.trim().length() > 0) {
return line;
}
}
}
/**
* vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
* items *CRLF
* "END" [ws] ":" [ws] "VCARD"
*/
private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException {
boolean allowGarbage = false;
if (firstReading) {
if (mNestCount > 0) {
for (int i = 0; i < mNestCount; i++) {
if (!readBeginVCard(allowGarbage)) {
return false;
}
allowGarbage = true;
}
}
}
if (!readBeginVCard(allowGarbage)) {
return false;
}
long start;
if (mBuilder != null) {
start = System.currentTimeMillis();
mBuilder.startEntry();
mTimeReadStartRecord += System.currentTimeMillis() - start;
}
start = System.currentTimeMillis();
parseItems();
mTimeParseItems += System.currentTimeMillis() - start;
readEndVCard(true, false);
if (mBuilder != null) {
start = System.currentTimeMillis();
mBuilder.endEntry();
mTimeReadEndRecord += System.currentTimeMillis() - start;
}
return true;
}
/**
* @return True when successful. False when reaching the end of line
* @throws IOException
* @throws VCardException
*/
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
String line;
do {
while (true) {
line = getLine();
if (line == null) {
return false;
} else if (line.trim().length() > 0) {
break;
}
}
String[] strArray = line.split(":", 2);
int length = strArray.length;
// Though vCard 2.1/3.0 specification does not allow lower cases,
// vCard file emitted by some external vCard expoter have such invalid Strings.
// So we allow it.
// e.g. BEGIN:vCard
if (length == 2 &&
strArray[0].trim().equalsIgnoreCase("BEGIN") &&
strArray[1].trim().equalsIgnoreCase("VCARD")) {
return true;
} else if (!allowGarbage) {
if (mNestCount > 0) {
mPreviousLine = line;
return false;
} else {
throw new VCardException(
"Expected String \"BEGIN:VCARD\" did not come "
+ "(Instead, \"" + line + "\" came)");
}
}
} while(allowGarbage);
throw new VCardException("Reached where must not be reached.");
}
/**
* The arguments useCache and allowGarbase are usually true and false accordingly when
* this function is called outside this function itself.
*
* @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine()
* is used.
* @param allowGarbage When true, ignore non "END:VCARD" line.
* @throws IOException
* @throws VCardException
*/
protected void readEndVCard(boolean useCache, boolean allowGarbage)
throws IOException, VCardException { throws IOException, VCardException {
String line; mVCardParserImpl.parse(is, interepreter);
do {
if (useCache) {
// Though vCard specification does not allow lower cases,
// some data may have them, so we allow it.
line = mPreviousLine;
} else {
while (true) {
line = getLine();
if (line == null) {
throw new VCardException("Expected END:VCARD was not found.");
} else if (line.trim().length() > 0) {
break;
}
}
}
String[] strArray = line.split(":", 2);
if (strArray.length == 2 &&
strArray[0].trim().equalsIgnoreCase("END") &&
strArray[1].trim().equalsIgnoreCase("VCARD")) {
return;
} else if (!allowGarbage) {
throw new VCardException("END:VCARD != \"" + mPreviousLine + "\"");
}
useCache = false;
} while (allowGarbage);
}
/**
* items = *CRLF item
* / item
*/
protected void parseItems() throws IOException, VCardException {
boolean ended = false;
if (mBuilder != null) {
long start = System.currentTimeMillis();
mBuilder.startProperty();
mTimeStartProperty += System.currentTimeMillis() - start;
}
ended = parseItem();
if (mBuilder != null && !ended) {
long start = System.currentTimeMillis();
mBuilder.endProperty();
mTimeEndProperty += System.currentTimeMillis() - start;
}
while (!ended) {
// follow VCARD ,it wont reach endProperty
if (mBuilder != null) {
long start = System.currentTimeMillis();
mBuilder.startProperty();
mTimeStartProperty += System.currentTimeMillis() - start;
}
try {
ended = parseItem();
} catch (VCardInvalidCommentLineException e) {
Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
ended = false;
}
if (mBuilder != null && !ended) {
long start = System.currentTimeMillis();
mBuilder.endProperty();
mTimeEndProperty += System.currentTimeMillis() - start;
}
}
}
/**
* item = [groups "."] name [params] ":" value CRLF
* / [groups "."] "ADR" [params] ":" addressparts CRLF
* / [groups "."] "ORG" [params] ":" orgparts CRLF
* / [groups "."] "N" [params] ":" nameparts CRLF
* / [groups "."] "AGENT" [params] ":" vcard CRLF
*/
protected boolean parseItem() throws IOException, VCardException {
mEncoding = sDefaultEncoding;
final String line = getNonEmptyLine();
long start = System.currentTimeMillis();
String[] propertyNameAndValue = separateLineAndHandleGroup(line);
if (propertyNameAndValue == null) {
return true;
}
if (propertyNameAndValue.length != 2) {
throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
}
String propertyName = propertyNameAndValue[0].toUpperCase();
String propertyValue = propertyNameAndValue[1];
mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start;
if (propertyName.equals("ADR") || propertyName.equals("ORG") ||
propertyName.equals("N")) {
start = System.currentTimeMillis();
handleMultiplePropertyValue(propertyName, propertyValue);
mTimeParseAdrOrgN += System.currentTimeMillis() - start;
return false;
} else if (propertyName.equals("AGENT")) {
handleAgent(propertyValue);
return false;
} else if (isValidPropertyName(propertyName)) {
if (propertyName.equals("BEGIN")) {
if (propertyValue.equals("VCARD")) {
throw new VCardNestedException("This vCard has nested vCard data in it.");
} else {
throw new VCardException("Unknown BEGIN type: " + propertyValue);
}
} else if (propertyName.equals("VERSION") &&
!propertyValue.equals(getVersionString())) {
throw new VCardVersionException("Incompatible version: " +
propertyValue + " != " + getVersionString());
}
start = System.currentTimeMillis();
handlePropertyValue(propertyName, propertyValue);
mTimeParsePropertyValues += System.currentTimeMillis() - start;
return false;
}
throw new VCardException("Unknown property name: \"" + propertyName + "\"");
} }
static private final int STATE_GROUP_OR_PROPNAME = 0; public void cancel() {
static private final int STATE_PARAMS = 1; mVCardParserImpl.cancel();
// vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not.
// This is just for safety.
static private final int STATE_PARAMS_IN_DQUOTE = 2;
protected String[] separateLineAndHandleGroup(String line) throws VCardException {
int state = STATE_GROUP_OR_PROPNAME;
int nameIndex = 0;
final String[] propertyNameAndValue = new String[2];
final int length = line.length();
if (length > 0 && line.charAt(0) == '#') {
throw new VCardInvalidCommentLineException();
}
for (int i = 0; i < length; i++) {
char ch = line.charAt(i);
switch (state) {
case STATE_GROUP_OR_PROPNAME: {
if (ch == ':') {
final String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
return null;
}
if (mBuilder != null) {
mBuilder.propertyName(propertyName);
}
propertyNameAndValue[0] = propertyName;
if (i < length - 1) {
propertyNameAndValue[1] = line.substring(i + 1);
} else {
propertyNameAndValue[1] = "";
}
return propertyNameAndValue;
} else if (ch == '.') {
String groupName = line.substring(nameIndex, i);
if (mBuilder != null) {
mBuilder.propertyGroup(groupName);
}
nameIndex = i + 1;
} else if (ch == ';') {
String propertyName = line.substring(nameIndex, i);
if (propertyName.equalsIgnoreCase("END")) {
mPreviousLine = line;
return null;
}
if (mBuilder != null) {
mBuilder.propertyName(propertyName);
}
propertyNameAndValue[0] = propertyName;
nameIndex = i + 1;
state = STATE_PARAMS;
}
break;
}
case STATE_PARAMS: {
if (ch == '"') {
state = STATE_PARAMS_IN_DQUOTE;
} else if (ch == ';') {
handleParams(line.substring(nameIndex, i));
nameIndex = i + 1;
} else if (ch == ':') {
handleParams(line.substring(nameIndex, i));
if (i < length - 1) {
propertyNameAndValue[1] = line.substring(i + 1);
} else {
propertyNameAndValue[1] = "";
}
return propertyNameAndValue;
}
break;
}
case STATE_PARAMS_IN_DQUOTE: {
if (ch == '"') {
state = STATE_PARAMS;
}
break;
}
}
}
throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
}
/**
* params = ";" [ws] paramlist
* paramlist = paramlist [ws] ";" [ws] param
* / param
* param = "TYPE" [ws] "=" [ws] ptypeval
* / "VALUE" [ws] "=" [ws] pvalueval
* / "ENCODING" [ws] "=" [ws] pencodingval
* / "CHARSET" [ws] "=" [ws] charsetval
* / "LANGUAGE" [ws] "=" [ws] langval
* / "X-" word [ws] "=" [ws] word
* / knowntype
*/
protected void handleParams(String params) throws VCardException {
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
final String paramName = strArray[0].trim().toUpperCase();
String paramValue = strArray[1].trim();
if (paramName.equals("TYPE")) {
handleType(paramValue);
} else if (paramName.equals("VALUE")) {
handleValue(paramValue);
} else if (paramName.equals("ENCODING")) {
handleEncoding(paramValue);
} else if (paramName.equals("CHARSET")) {
handleCharset(paramValue);
} else if (paramName.equals("LANGUAGE")) {
handleLanguage(paramValue);
} else if (paramName.startsWith("X-")) {
handleAnyParam(paramName, paramValue);
} else {
throw new VCardException("Unknown type \"" + paramName + "\"");
}
} else {
handleParamWithoutName(strArray[0]);
}
}
/**
* vCard 3.0 parser may throw VCardException.
*/
@SuppressWarnings("unused")
protected void handleParamWithoutName(final String paramValue) throws VCardException {
handleType(paramValue);
}
/**
* ptypeval = knowntype / "X-" word
*/
protected void handleType(final String ptypeval) {
String upperTypeValue = ptypeval;
if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) &&
!mUnknownTypeMap.contains(ptypeval)) {
mUnknownTypeMap.add(ptypeval);
Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("TYPE");
mBuilder.propertyParamValue(upperTypeValue);
}
}
/**
* pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
*/
protected void handleValue(final String pvalueval) {
if (!sKnownValueSet.contains(pvalueval.toUpperCase()) &&
pvalueval.startsWith("X-") &&
!mUnknownValueMap.contains(pvalueval)) {
mUnknownValueMap.add(pvalueval);
Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval);
}
if (mBuilder != null) {
mBuilder.propertyParamType("VALUE");
mBuilder.propertyParamValue(pvalueval);
}
}
/**
* pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
*/
protected void handleEncoding(String pencodingval) throws VCardException {
if (isValidEncoding(pencodingval) ||
pencodingval.startsWith("X-")) {
if (mBuilder != null) {
mBuilder.propertyParamType("ENCODING");
mBuilder.propertyParamValue(pencodingval);
}
mEncoding = pencodingval;
} else {
throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
}
}
/**
* vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
* but today's vCard often contains other charset, so we allow them.
*/
protected void handleCharset(String charsetval) {
if (mBuilder != null) {
mBuilder.propertyParamType("CHARSET");
mBuilder.propertyParamValue(charsetval);
}
}
/**
* See also Section 7.1 of RFC 1521
*/
protected void handleLanguage(String langval) throws VCardException {
String[] strArray = langval.split("-");
if (strArray.length != 2) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
String tmp = strArray[0];
int length = tmp.length();
for (int i = 0; i < length; i++) {
if (!isLetter(tmp.charAt(i))) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
}
tmp = strArray[1];
length = tmp.length();
for (int i = 0; i < length; i++) {
if (!isLetter(tmp.charAt(i))) {
throw new VCardException("Invalid Language: \"" + langval + "\"");
}
}
if (mBuilder != null) {
mBuilder.propertyParamType("LANGUAGE");
mBuilder.propertyParamValue(langval);
}
}
/**
* Mainly for "X-" type. This accepts any kind of type without check.
*/
protected void handleAnyParam(String paramName, String paramValue) {
if (mBuilder != null) {
mBuilder.propertyParamType(paramName);
mBuilder.propertyParamValue(paramValue);
}
}
protected void handlePropertyValue(String propertyName, String propertyValue)
throws IOException, VCardException {
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
final long start = System.currentTimeMillis();
final String result = getQuotedPrintable(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
mBuilder.propertyValues(v);
}
mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
} else if (mEncoding.equalsIgnoreCase("BASE64") ||
mEncoding.equalsIgnoreCase("B")) {
final long start = System.currentTimeMillis();
// It is very rare, but some BASE64 data may be so big that
// OutOfMemoryError occurs. To ignore such cases, use try-catch.
try {
final String result = getBase64(propertyValue);
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(result);
mBuilder.propertyValues(v);
}
} catch (OutOfMemoryError error) {
Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
if (mBuilder != null) {
mBuilder.propertyValues(null);
}
}
mTimeHandleBase64 += System.currentTimeMillis() - start;
} else {
if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT")
|| mEncoding.equalsIgnoreCase("8BIT")
|| mEncoding.toUpperCase().startsWith("X-"))) {
Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\".");
}
final long start = System.currentTimeMillis();
if (mBuilder != null) {
ArrayList<String> v = new ArrayList<String>();
v.add(maybeUnescapeText(propertyValue));
mBuilder.propertyValues(v);
}
mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
}
}
protected String getQuotedPrintable(String firstString) throws IOException, VCardException {
// Specifically, there may be some padding between = and CRLF.
// See the following:
//
// qp-line := *(qp-segment transport-padding CRLF)
// qp-part transport-padding
// qp-segment := qp-section *(SPACE / TAB) "="
// ; Maximum length of 76 characters
//
// e.g. (from RFC 2045)
// Now's the time =
// for all folk to come=
// to the aid of their country.
if (firstString.trim().endsWith("=")) {
// remove "transport-padding"
int pos = firstString.length() - 1;
while(firstString.charAt(pos) != '=') {
}
StringBuilder builder = new StringBuilder();
builder.append(firstString.substring(0, pos + 1));
builder.append("\r\n");
String line;
while (true) {
line = getLine();
if (line == null) {
throw new VCardException(
"File ended during parsing quoted-printable String");
}
if (line.trim().endsWith("=")) {
// remove "transport-padding"
pos = line.length() - 1;
while(line.charAt(pos) != '=') {
}
builder.append(line.substring(0, pos + 1));
builder.append("\r\n");
} else {
builder.append(line);
break;
}
}
return builder.toString();
} else {
return firstString;
}
}
protected String getBase64(String firstString) throws IOException, VCardException {
StringBuilder builder = new StringBuilder();
builder.append(firstString);
while (true) {
String line = getLine();
if (line == null) {
throw new VCardException(
"File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;
}
builder.append(line);
}
return builder.toString();
}
/**
* Mainly for "ADR", "ORG", and "N"
* We do not care the number of strnosemi here.
*
* addressparts = 0*6(strnosemi ";") strnosemi
* ; PO Box, Extended Addr, Street, Locality, Region,
* Postal Code, Country Name
* orgparts = *(strnosemi ";") strnosemi
* ; First is Organization Name,
* remainder are Organization Units.
* nameparts = 0*4(strnosemi ";") strnosemi
* ; Family, Given, Middle, Prefix, Suffix.
* ; Example:Public;John;Q.;Reverend Dr.;III, Esq.
* strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi
* ; To include a semicolon in this string, it must be escaped
* ; with a "\" character.
*
* We are not sure whether we should add "\" CRLF to each value.
* For now, we exclude them.
*/
protected void handleMultiplePropertyValue(String propertyName, String propertyValue)
throws IOException, VCardException {
// vCard 2.1 does not allow QUOTED-PRINTABLE here,
// but some softwares/devices emit such data.
if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
propertyValue = getQuotedPrintable(propertyValue);
}
if (mBuilder != null) {
mBuilder.propertyValues(VCardUtils.constructListFromValue(
propertyValue, (getVersion() == VCardConfig.FLAG_V30)));
}
}
/**
* vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all.
*
* item = ...
* / [groups "."] "AGENT"
* [params] ":" vcard CRLF
* vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF
* items *CRLF "END" [ws] ":" [ws] "VCARD"
*/
protected void handleAgent(final String propertyValue) throws VCardException {
if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) {
// Apparently invalid line seen in Windows Mobile 6.5. Ignore them.
return;
} else {
throw new VCardAgentNotSupportedException("AGENT Property is not supported now.");
}
// TODO: Support AGENT property.
}
/**
* For vCard 3.0.
*/
protected String maybeUnescapeText(final 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(final char ch) {
return unescapeCharacter(ch);
}
public static String unescapeCharacter(final 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.
if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') {
return String.valueOf(ch);
} else {
return null;
}
}
@Override
public boolean parse(final InputStream is, final VCardInterpreter builder)
throws IOException, VCardException {
return parse(is, VCardConfig.DEFAULT_CHARSET, builder);
}
@Override
public boolean parse(InputStream is, String charset, VCardInterpreter builder)
throws IOException, VCardException {
if (charset == null) {
charset = VCardConfig.DEFAULT_CHARSET;
}
final InputStreamReader tmpReader = new InputStreamReader(is, charset);
if (VCardConfig.showPerformanceLog()) {
mReader = new CustomBufferedReader(tmpReader);
} else {
mReader = new BufferedReader(tmpReader);
}
mBuilder = builder;
long start = System.currentTimeMillis();
if (mBuilder != null) {
mBuilder.start();
}
parseVCardFile();
if (mBuilder != null) {
mBuilder.end();
}
mTimeTotal += System.currentTimeMillis() - start;
if (VCardConfig.showPerformanceLog()) {
showPerformanceInfo();
}
return true;
}
@Override
public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled)
throws IOException, VCardException {
mCanceled = canceled;
parse(is, charset, builder);
}
private void showPerformanceInfo() {
Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
if (mReader instanceof CustomBufferedReader) {
Log.d(LOG_TAG, "Total readLine time: " +
((CustomBufferedReader)mReader).getTotalmillisecond() + " ms");
}
Log.d(LOG_TAG, "Time for handling the beggining of the record: " +
mTimeReadStartRecord + " ms");
Log.d(LOG_TAG, "Time for handling the end of the record: " +
mTimeReadEndRecord + " ms");
Log.d(LOG_TAG, "Time for parsing line, and handling group: " +
mTimeParseLineAndHandleGroup + " ms");
Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms");
Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms");
Log.d(LOG_TAG, "Time for handling normal property values: " +
mTimeHandleMiscPropertyValue + " ms");
Log.d(LOG_TAG, "Time for handling Quoted-Printable: " +
mTimeHandleQuotedPrintable + " ms");
Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms");
}
private boolean isLetter(char ch) {
if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
return true;
}
return false;
}
}
class CustomBufferedReader extends BufferedReader {
private long mTime;
public CustomBufferedReader(Reader in) {
super(in);
}
@Override
public String readLine() throws IOException {
long start = System.currentTimeMillis();
String ret = super.readLine();
long end = System.currentTimeMillis();
mTime += end - start;
return ret;
}
public long getTotalmillisecond() {
return mTime;
} }
} }

View File

@@ -16,343 +16,72 @@
package android.pim.vcard; package android.pim.vcard;
import android.pim.vcard.exception.VCardException; import android.pim.vcard.exception.VCardException;
import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set;
/** /**
* The class used to parse vCard 3.0. * <p>
* Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426). * vCard parser for vCard 3.0. See RFC 2426 for more detail.
* </p>
* <p>
* This parser allows vCard format which is not allowed in the RFC, since
* we have seen several vCard 3.0 files which don't comply with it.
* </p>
* <p>
* e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files
* have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426,
* but it is not a must. We silently allow "CHARSET".
* </p>
*/ */
public class VCardParser_V30 extends VCardParser_V21 { public class VCardParser_V30 implements VCardParser {
private static final String LOG_TAG = "VCardParser_V30"; /* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
Arrays.asList(
"BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
"VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
"BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1 "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS", "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
"SORT-STRING", "CATEGORIES", "PRODID")); // 3.0 "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
// Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety.
private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>(
Arrays.asList("7BIT", "8BIT", "BASE64", "B"));
// Although RFC 2426 specifies some property must not have parameters, we allow it,
// since there may be some careers which violates the RFC...
private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>();
private String mPreviousLine;
private boolean mEmittedAgentWarning = false;
/** /**
* True when the caller wants the parser to be strict about the input. * <p>
* Currently this is only for testing. * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0.
* </p>
* <p>
* Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety.
* </p>
* <p>
* "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either,
* because the encoding ambiguates how the vCard file to be parsed.
* </p>
*/ */
private final boolean mStrictParsing; /* package */ static final Set<String> sAcceptableEncoding =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
VCardConstants.PARAM_ENCODING_7BIT,
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_BASE64,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V30 mVCardParserImpl;
public VCardParser_V30() { public VCardParser_V30() {
super(); mVCardParserImpl = new VCardParserImpl_V30();
mStrictParsing = false;
} }
/** public VCardParser_V30(int vcardType) {
* @param strictParsing when true, this object throws VCardException when the vcard is not mVCardParserImpl = new VCardParserImpl_V30(vcardType);
* valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class
* is not fully yet for being used with this flag and may not notice invalid line(s).
*
* @hide currently only for testing!
*/
public VCardParser_V30(boolean strictParsing) {
super();
mStrictParsing = strictParsing;
} }
public VCardParser_V30(int parseMode) { public void parse(InputStream is, VCardInterpreter interepreter)
super(parseMode);
mStrictParsing = false;
}
@Override
protected int getVersion() {
return VCardConfig.FLAG_V30;
}
@Override
protected String getVersionString() {
return VCardConstants.VERSION_V30;
}
@Override
protected boolean isValidPropertyName(String propertyName) {
if (!(sAcceptablePropsWithParam.contains(propertyName) ||
acceptablePropsWithoutParam.contains(propertyName) ||
propertyName.startsWith("X-")) &&
!mUnknownTypeMap.contains(propertyName)) {
mUnknownTypeMap.add(propertyName);
Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName);
}
return true;
}
@Override
protected boolean isValidEncoding(String encoding) {
return sAcceptableEncodingV30.contains(encoding.toUpperCase());
}
@Override
protected String getLine() throws IOException {
if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
} else {
return mReader.readLine();
}
}
/**
* vCard 3.0 requires that the line with space at the beginning of the line
* must be combined with previous line.
*/
@Override
protected String getNonEmptyLine() throws IOException, VCardException {
String line;
StringBuilder builder = null;
while (true) {
line = mReader.readLine();
if (line == null) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
throw new VCardException("Reached end of buffer.");
} else if (line.length() == 0) {
if (builder != null) {
return builder.toString();
} else if (mPreviousLine != null) {
String ret = mPreviousLine;
mPreviousLine = null;
return ret;
}
} else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') {
if (builder != null) {
// See Section 5.8.1 of RFC 2425 (MIME-DIR document).
// Following is the excerpts from it.
//
// DESCRIPTION:This is a long description that exists on a long line.
//
// Can be represented as:
//
// DESCRIPTION:This is a long description
// that exists on a long line.
//
// It could also be represented as:
//
// DESCRIPTION:This is a long descrip
// tion that exists o
// n a long line.
builder.append(line.substring(1));
} else if (mPreviousLine != null) {
builder = new StringBuilder();
builder.append(mPreviousLine);
mPreviousLine = null;
builder.append(line.substring(1));
} else {
throw new VCardException("Space exists at the beginning of the line");
}
} else {
if (mPreviousLine == null) {
mPreviousLine = line;
if (builder != null) {
return builder.toString();
}
} else {
String ret = mPreviousLine;
mPreviousLine = line;
return ret;
}
}
}
}
/**
* vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF
* 1 * (contentline)
* ;A vCard object MUST include the VERSION, FN and N types.
* [group "."] "END" ":" "VCARD" 1 * CRLF
*/
@Override
protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException {
// TODO: vCard 3.0 supports group.
return super.readBeginVCard(allowGarbage);
}
@Override
protected void readEndVCard(boolean useCache, boolean allowGarbage)
throws IOException, VCardException { throws IOException, VCardException {
// TODO: vCard 3.0 supports group. mVCardParserImpl.parse(is, interepreter);
super.readEndVCard(useCache, allowGarbage);
} }
/** public void cancel() {
* vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not. mVCardParserImpl.cancel();
*/
@Override
protected void handleParams(String params) throws VCardException {
try {
super.handleParams(params);
} catch (VCardException e) {
// maybe IANA type
String[] strArray = params.split("=", 2);
if (strArray.length == 2) {
handleAnyParam(strArray[0], strArray[1]);
} else {
// Must not come here in the current implementation.
throw new VCardException(
"Unknown params value: " + params);
}
}
}
@Override
protected void handleAnyParam(String paramName, String paramValue) {
super.handleAnyParam(paramName, paramValue);
}
@Override
protected void handleParamWithoutName(final String paramValue) throws VCardException {
if (mStrictParsing) {
throw new VCardException("Parameter without name is not acceptable in vCard 3.0");
} else {
super.handleParamWithoutName(paramValue);
}
}
/**
* vCard 3.0 defines
*
* param = param-name "=" param-value *("," param-value)
* param-name = iana-token / x-name
* param-value = ptext / quoted-string
* quoted-string = DQUOTE QSAFE-CHAR DQUOTE
*/
@Override
protected void handleType(String ptypevalues) {
String[] ptypeArray = ptypevalues.split(",");
mBuilder.propertyParamType("TYPE");
for (String value : ptypeArray) {
int length = value.length();
if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
mBuilder.propertyParamValue(value.substring(1, value.length() - 1));
} else {
mBuilder.propertyParamValue(value);
}
}
}
@Override
protected void handleAgent(String propertyValue) {
// The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1.
//
// e.g.
// AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n
// TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n
// ET:jfriday@host.com\nEND:VCARD\n
//
// TODO: fix this.
//
// issue:
// vCard 3.0 also allows this as an example.
//
// AGENT;VALUE=uri:
// CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com
//
// This is not vCard. Should we support this?
//
// Just ignore the line for now, since we cannot know how to handle it...
if (!mEmittedAgentWarning) {
Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it");
mEmittedAgentWarning = true;
}
}
/**
* vCard 3.0 does not require two CRLF at the last of BASE64 data.
* It only requires that data should be MIME-encoded.
*/
@Override
protected String getBase64(String firstString) throws IOException, VCardException {
StringBuilder builder = new StringBuilder();
builder.append(firstString);
while (true) {
String line = getLine();
if (line == null) {
throw new VCardException(
"File ended during parsing BASE64 binary");
}
if (line.length() == 0) {
break;
} else if (!line.startsWith(" ") && !line.startsWith("\t")) {
mPreviousLine = line;
break;
}
builder.append(line);
}
return builder.toString();
}
/**
* ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N")
* ; \\ encodes \, \n or \N encodes newline
* ; \; encodes ;, \, encodes ,
*
* Note: Apple escapes ':' into '\:' while does not escape '\'
*/
@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++) {
char ch = text.charAt(i);
if (ch == '\\' && i < length - 1) {
char next_ch = text.charAt(++i);
if (next_ch == 'n' || next_ch == 'N') {
builder.append("\n");
} else {
builder.append(next_ch);
}
} else {
builder.append(ch);
}
}
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

@@ -0,0 +1,78 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* <p>
* vCard parser for vCard 4.0.
* </p>
* <p>
* Currently this parser is based on vCard 4.0 specification rev 11.
* </p>
*/
public class VCardParser_V40 implements VCardParser {
/* package */ static final Set<String> sKnownPropertyNameSet =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"BEGIN", "END", "SOURCE", "NAME", "KIND", "XML",
"FN", "N", "NICKNAME", "PHOTO", "BDAY", "DDAY",
"BIRTH", "DEATH", "ANNIVERSARY", "SEX", "ADR",
"LABEL", "TEL", "EMAIL", "IMPP", "LANG", "TZ",
"GEO", "TITLE", "ROLE", "LOGO", "ORG", "MEMBER",
"RELATED", "CATEGORIES", "NOTE", "PRODID",
"REV", "SOUND", "UID", "CLIENTPIDMAP",
"URL", "VERSION", "CLASS", "KEY", "FBURL", "CALENDRURI",
"CALURI")));
/**
* <p>
* A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0.
* </p>
*/
/* package */ static final Set<String> sAcceptableEncoding =
Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
VCardConstants.PARAM_ENCODING_8BIT,
VCardConstants.PARAM_ENCODING_B)));
private final VCardParserImpl_V30 mVCardParserImpl;
public VCardParser_V40() {
mVCardParserImpl = new VCardParserImpl_V40();
}
public VCardParser_V40(int vcardType) {
mVCardParserImpl = new VCardParserImpl_V40(vcardType);
}
@Override
public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
mVCardParserImpl.parse(is, interepreter);
}
@Override
public void cancel() {
mVCardParserImpl.cancel();
}
}

View File

@@ -15,17 +15,33 @@
*/ */
package android.pim.vcard; package android.pim.vcard;
import android.text.TextUtils;
import android.util.Log;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* Class which tries to detects the source of the vCard from its properties. * <p>
* Currently this implementation is very premature. * The class which tries to detects the source of a vCard file from its contents.
* @hide * </p>
* <p>
* The specification of vCard (including both 2.1 and 3.0) is not so strict as to
* guess its format just by reading beginning few lines (usually we can, but in
* some most pessimistic case, we cannot until at almost the end of the file).
* Also we cannot store all vCard entries in memory, while there's no specification
* how big the vCard entry would become after the parse.
* </p>
* <p>
* This class is usually used for the "first scan", in which we can understand which vCard
* version is used (and how many entries exist in a file).
* </p>
*/ */
public class VCardSourceDetector implements VCardInterpreter { public class VCardSourceDetector implements VCardInterpreter {
private static final String LOG_TAG = "VCardSourceDetector";
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList( private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME", "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID")); "X-ABADR", "X-ABUID"));
@@ -42,10 +58,30 @@ public class VCardSourceDetector implements VCardInterpreter {
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED", "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
"X-SD-DESCRIPTION")); "X-SD-DESCRIPTION"));
private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE"; private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE";
private int mType = VCardConfig.PARSE_TYPE_UNKNOWN; /**
* Represents that no estimation is available. Users of this class is able to this
* constant when you don't want to let a vCard parser rely on estimation for parse type.
*/
public static final int PARSE_TYPE_UNKNOWN = 0;
// For Apple's software, which does not mean this type is effective for all its products.
// We confirmed they usually use UTF-8, but not sure about vCard type.
private static final int PARSE_TYPE_APPLE = 1;
// For Japanese mobile phones, which are usually using Shift_JIS as a charset.
private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2;
// For some of mobile phones released from DoCoMo, which use nested vCard.
private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3;
// For Japanese Windows Mobel phones. It's version is supposed to be 6.5.
private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4;
private int mParseType = 0; // Not sure.
private boolean mNeedToParseVersion = false;
private int mVersion = -1; // -1 == unknown
// Some mobile phones (like FOMA) tells us the charset of the data. // Some mobile phones (like FOMA) tells us the charset of the data.
private boolean mNeedParseSpecifiedCharset; private boolean mNeedToParseCharset;
private String mSpecifiedCharset; private String mSpecifiedCharset;
public void start() { public void start() {
@@ -58,7 +94,8 @@ public class VCardSourceDetector implements VCardInterpreter {
} }
public void startProperty() { public void startProperty() {
mNeedParseSpecifiedCharset = false; mNeedToParseCharset = false;
mNeedToParseVersion = false;
} }
public void endProperty() { public void endProperty() {
@@ -71,22 +108,26 @@ public class VCardSourceDetector implements VCardInterpreter {
} }
public void propertyName(String name) { public void propertyName(String name) {
if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) { if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) {
mType = VCardConfig.PARSE_TYPE_FOMA; mNeedToParseVersion = true;
mNeedParseSpecifiedCharset = true; return;
} else if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
// Probably Shift_JIS is used, but we should double confirm.
mNeedToParseCharset = true;
return; return;
} }
if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) { if (mParseType != PARSE_TYPE_UNKNOWN) {
return; return;
} }
if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) { if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) {
mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP; mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP;
} else if (FOMA_SIGNS.contains(name)) { } else if (FOMA_SIGNS.contains(name)) {
mType = VCardConfig.PARSE_TYPE_FOMA; mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
} else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) { } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) {
mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP; mParseType = PARSE_TYPE_MOBILE_PHONE_JP;
} else if (APPLE_SIGNS.contains(name)) { } else if (APPLE_SIGNS.contains(name)) {
mType = VCardConfig.PARSE_TYPE_APPLE; mParseType = PARSE_TYPE_APPLE;
} }
} }
@@ -97,30 +138,65 @@ public class VCardSourceDetector implements VCardInterpreter {
} }
public void propertyValues(List<String> values) { public void propertyValues(List<String> values) {
if (mNeedParseSpecifiedCharset && values.size() > 0) { if (mNeedToParseVersion && values.size() > 0) {
final String versionString = values.get(0);
if (versionString.equals(VCardConstants.VERSION_V21)) {
mVersion = VCardConfig.VERSION_21;
} else if (versionString.equals(VCardConstants.VERSION_V30)) {
mVersion = VCardConfig.VERSION_30;
} else if (versionString.equals(VCardConstants.VERSION_V40)) {
mVersion = VCardConfig.VERSION_40;
} else {
Log.w(LOG_TAG, "Invalid version string: " + versionString);
}
} else if (mNeedToParseCharset && values.size() > 0) {
mSpecifiedCharset = values.get(0); mSpecifiedCharset = values.get(0);
} }
} }
/* package */ int getEstimatedType() {
return mType;
}
/** /**
* Return charset String guessed from the source's properties. * @return The available type can be used with vCard parser. You probably need to
* use {{@link #getEstimatedCharset()} to understand the charset to be used.
*/
public int getEstimatedType() {
switch (mParseType) {
case PARSE_TYPE_DOCOMO_TORELATE_NEST:
return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST;
case PARSE_TYPE_MOBILE_PHONE_JP:
return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE;
case PARSE_TYPE_APPLE:
case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
default: {
if (mVersion == VCardConfig.VERSION_21) {
return VCardConfig.VCARD_TYPE_V21_GENERIC;
} else if (mVersion == VCardConfig.VERSION_30) {
return VCardConfig.VCARD_TYPE_V30_GENERIC;
} else if (mVersion == VCardConfig.VERSION_40) {
return VCardConfig.VCARD_TYPE_V40_GENERIC;
} else {
return VCardConfig.VCARD_TYPE_UNKNOWN;
}
}
}
}
/**
* <p>
* Returns charset String guessed from the source's properties.
* This method must be called after parsing target file(s). * This method must be called after parsing target file(s).
* </p>
* @return Charset String. Null is returned if guessing the source fails. * @return Charset String. Null is returned if guessing the source fails.
*/ */
public String getEstimatedCharset() { public String getEstimatedCharset() {
if (mSpecifiedCharset != null) { if (TextUtils.isEmpty(mSpecifiedCharset)) {
return mSpecifiedCharset; return mSpecifiedCharset;
} }
switch (mType) { switch (mParseType) {
case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP: case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
case VCardConfig.PARSE_TYPE_FOMA: case PARSE_TYPE_DOCOMO_TORELATE_NEST:
case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP: case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS"; return "SHIFT_JIS";
case VCardConfig.PARSE_TYPE_APPLE: case PARSE_TYPE_APPLE:
return "UTF-8"; return "UTF-8";
default: default:
return null; return null;

View File

@@ -16,13 +16,21 @@
package android.pim.vcard; package android.pim.vcard;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils; import android.telephony.PhoneNumberUtils;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.net.QuotedPrintableCodec;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
@@ -36,6 +44,8 @@ import java.util.Set;
* Utilities for VCard handling codes. * Utilities for VCard handling codes.
*/ */
public class VCardUtils { public class VCardUtils {
private static final String LOG_TAG = "VCardUtils";
// Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is
// converted to two parameter Strings. These only contain some minor fields valid in both // converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure. // vCard and current (as of 2009-08-07) Contacts structure.
@@ -185,8 +195,7 @@ public class VCardUtils {
// For backward compatibility. // For backward compatibility.
// Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now. // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now.
// To support mobile type at that time, this custom label had been used. // To support mobile type at that time, this custom label had been used.
return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label) return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
|| sMobilePhoneLabelSet.contains(label));
} }
public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) { public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
@@ -240,10 +249,13 @@ public class VCardUtils {
} }
/** /**
* <p>
* Inserts postal data into the builder object. * Inserts postal data into the builder object.
* * </p>
* <p>
* Note that the data structure of ContactsContract is different from that defined in vCard. * Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method. * So some conversion may be performed in this method.
* </p>
*/ */
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType, public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder, final ContentProviderOperation.Builder builder,
@@ -319,18 +331,33 @@ public class VCardUtils {
return builder.toString(); return builder.toString();
} }
/**
* Splits the given value into pieces using the delimiter ';' inside it.
*
* Escaped characters in those values are automatically unescaped into original form.
*/
public static List<String> constructListFromValue(final String value, public static List<String> constructListFromValue(final String value,
final boolean isV30) { final int vcardType) {
final List<String> list = new ArrayList<String>(); final List<String> list = new ArrayList<String>();
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int length = value.length(); final int length = value.length();
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
char ch = value.charAt(i); char ch = value.charAt(i);
if (ch == '\\' && i < length - 1) { if (ch == '\\' && i < length - 1) {
char nextCh = value.charAt(i + 1); char nextCh = value.charAt(i + 1);
final String unescapedString = final String unescapedString;
(isV30 ? VCardParser_V30.unescapeCharacter(nextCh) : if (VCardConfig.isVersion40(vcardType)) {
VCardParser_V21.unescapeCharacter(nextCh)); unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh);
} else if (VCardConfig.isVersion30(vcardType)) {
unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh);
} else {
if (!VCardConfig.isVersion21(vcardType)) {
// Unknown vCard type
Log.w(LOG_TAG, "Unknown vCard type");
}
unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh);
}
if (unescapedString != null) { if (unescapedString != null) {
builder.append(unescapedString); builder.append(unescapedString);
i++; i++;
@@ -371,9 +398,13 @@ public class VCardUtils {
} }
/** /**
* <p>
* This is useful when checking the string should be encoded into quoted-printable * This is useful when checking the string should be encoded into quoted-printable
* or not, which is required by vCard 2.1. * or not, which is required by vCard 2.1.
* </p>
* <p>
* See the definition of "7bit" in vCard 2.1 spec for more information. * See the definition of "7bit" in vCard 2.1 spec for more information.
* </p>
*/ */
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) { public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
if (values == null) { if (values == null) {
@@ -407,13 +438,16 @@ public class VCardUtils {
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' ')); new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
/** /**
* <p>
* This is useful since vCard 3.0 often requires the ("X-") properties and groups * This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen. * should contain only alphabets, digits, and hyphen.
* * </p>
* <p>
* Note: It is already known some devices (wrongly) outputs properties with characters * Note: It is already known some devices (wrongly) outputs properties with characters
* which should not be in the field. One example is "X-GOOGLE TALK". We accept * which should not be in the field. One example is "X-GOOGLE TALK". We accept
* such kind of input but must never output it unless the target is very specific * such kind of input but must never output it unless the target is very specific
* to the device which is able to parse the malformed input. * to the device which is able to parse the malformed input.
* </p>
*/ */
public static boolean containsOnlyAlphaDigitHyphen(final String...values) { public static boolean containsOnlyAlphaDigitHyphen(final String...values) {
if (values == null) { if (values == null) {
@@ -451,14 +485,39 @@ public class VCardUtils {
return true; return true;
} }
public static boolean containsOnlyWhiteSpaces(final String...values) {
if (values == null) {
return true;
}
return containsOnlyWhiteSpaces(Arrays.asList(values));
}
public static boolean containsOnlyWhiteSpaces(final Collection<String> values) {
if (values == null) {
return true;
}
for (final String str : values) {
if (TextUtils.isEmpty(str)) {
continue;
}
final int length = str.length();
for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) {
if (!Character.isWhitespace(str.codePointAt(i))) {
return false;
}
}
}
return true;
}
/** /**
* <P> * <p>
* Returns true when the given String is categorized as "word" specified in vCard spec 2.1. * Returns true when the given String is categorized as "word" specified in vCard spec 2.1.
* </P> * </p>
* <P> * <p>
* vCard 2.1 specifies:<BR /> * vCard 2.1 specifies:<br />
* word = &lt;any printable 7bit us-ascii except []=:., &gt; * word = &lt;any printable 7bit us-ascii except []=:., &gt;
* </P> * </p>
*/ */
public static boolean isV21Word(final String value) { public static boolean isV21Word(final String value) {
if (TextUtils.isEmpty(value)) { if (TextUtils.isEmpty(value)) {
@@ -477,6 +536,14 @@ public class VCardUtils {
return true; return true;
} }
private static final int[] sEscapeIndicatorsV30 = new int[]{
':', ';', ',', ' '
};
private static final int[] sEscapeIndicatorsV40 = new int[]{
';', ':'
};
/** /**
* <P> * <P>
* Returns String available as parameter value in vCard 3.0. * Returns String available as parameter value in vCard 3.0.
@@ -487,10 +554,18 @@ public class VCardUtils {
* This method checks whether the given String can be used without quotes. * This method checks whether the given String can be used without quotes.
* </P> * </P>
* <P> * <P>
* Note: We remove DQUOTE silently for now. * Note: We remove DQUOTE inside the given value silently for now.
* </P> * </P>
*/ */
public static String toStringAvailableAsV30ParameValue(String value) { public static String toStringAsV30ParamValue(String value) {
return toStringAsParamValue(value, sEscapeIndicatorsV30);
}
public static String toStringAsV40ParamValue(String value) {
return toStringAsParamValue(value, sEscapeIndicatorsV40);
}
private static String toStringAsParamValue(String value, final int[] escapeIndicators) {
if (TextUtils.isEmpty(value)) { if (TextUtils.isEmpty(value)) {
value = ""; value = "";
} }
@@ -506,12 +581,19 @@ public class VCardUtils {
continue; continue;
} }
builder.appendCodePoint(codePoint); builder.appendCodePoint(codePoint);
if (codePoint == ':' || codePoint == ',' || codePoint == ' ') { for (int indicator : escapeIndicators) {
needQuote = true; if (codePoint == indicator) {
needQuote = true;
break;
}
} }
} }
final String result = builder.toString(); final String result = builder.toString();
return ((needQuote || result.isEmpty()) ? ('"' + result + '"') : result); return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result))
? ""
: (needQuote ? ('"' + result + '"')
: result));
} }
public static String toHalfWidthString(final String orgString) { public static String toHalfWidthString(final String orgString) {
@@ -577,6 +659,138 @@ public class VCardUtils {
return true; return true;
} }
//// The methods bellow may be used by unit test.
/**
* Unquotes given Quoted-Printable value. value must not be null.
*/
public static String parseQuotedPrintable(
final String value, boolean strictLineBreaking,
String sourceCharset, String targetCharset) {
// "= " -> " ", "=\t" -> "\t".
// Previous code had done this replacement. Keep on the safe side.
final String quotedPrintable;
{
final StringBuilder builder = new StringBuilder();
final 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);
if (nextCh == ' ' || nextCh == '\t') {
builder.append(nextCh);
i++;
continue;
}
}
builder.append(ch);
}
quotedPrintable = builder.toString();
}
String[] lines;
if (strictLineBreaking) {
lines = quotedPrintable.split("\r\n");
} else {
StringBuilder builder = new StringBuilder();
final int length = quotedPrintable.length();
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < length; i++) {
char ch = quotedPrintable.charAt(i);
if (ch == '\n') {
list.add(builder.toString());
builder = new StringBuilder();
} else if (ch == '\r') {
list.add(builder.toString());
builder = new StringBuilder();
if (i < length - 1) {
char nextCh = quotedPrintable.charAt(i + 1);
if (nextCh == '\n') {
i++;
}
}
} else {
builder.append(ch);
}
}
final String lastLine = builder.toString();
if (lastLine.length() > 0) {
list.add(lastLine);
}
lines = list.toArray(new String[0]);
}
final StringBuilder builder = new StringBuilder();
for (String line : lines) {
if (line.endsWith("=")) {
line = line.substring(0, line.length() - 1);
}
builder.append(line);
}
final String rawString = builder.toString();
if (TextUtils.isEmpty(rawString)) {
Log.w(LOG_TAG, "Given raw string is empty.");
}
byte[] rawBytes = null;
try {
rawBytes = rawString.getBytes(sourceCharset);
} catch (UnsupportedEncodingException e) {
Log.w(LOG_TAG, "Failed to decode: " + sourceCharset);
rawBytes = rawString.getBytes();
}
byte[] decodedBytes = null;
try {
decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes);
} catch (DecoderException e) {
Log.e(LOG_TAG, "DecoderException is thrown.");
decodedBytes = rawBytes;
}
try {
return new String(decodedBytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return new String(decodedBytes);
}
}
public static final VCardParser getAppropriateParser(int vcardType)
throws VCardException {
if (VCardConfig.isVersion21(vcardType)) {
return new VCardParser_V21();
} else if (VCardConfig.isVersion30(vcardType)) {
return new VCardParser_V30();
} else if (VCardConfig.isVersion40(vcardType)) {
return new VCardParser_V40();
} else {
throw new VCardException("Version is not specified");
}
}
public static final String convertStringCharset(
String originalString, String sourceCharset, String targetCharset) {
if (sourceCharset.equalsIgnoreCase(targetCharset)) {
return originalString;
}
final Charset charset = Charset.forName(sourceCharset);
final ByteBuffer byteBuffer = charset.encode(originalString);
// byteBuffer.array() "may" return byte array which is larger than
// byteBuffer.remaining(). Here, we keep on the safe side.
final byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
try {
return new String(bytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return null;
}
}
// TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean
private VCardUtils() { private VCardUtils() {
} }
} }

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard.exception; package android.pim.vcard.exception;
/** /**

View File

@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard.exception; package android.pim.vcard.exception;
/** /**

View File

@@ -1,101 +0,0 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardEntry;
import android.pim.vcard.VCardEntryConstructor;
import android.pim.vcard.VCardEntryHandler;
import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/* package */ class ContentValuesVerifier implements VCardEntryHandler {
private AndroidTestCase mTestCase;
private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
new ArrayList<ContentValuesVerifierElem>();
private int mIndex;
public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
mTestCase = androidTestCase;
ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase);
mContentValuesVerifierElemList.add(importVerifier);
return importVerifier;
}
public void verify(int resId, int vCardType) throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId),
vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
vCardParser = new VCardParser_V30(true); // use StrictParsing
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
VCardEntryConstructor builder =
new VCardEntryConstructor(null, null, false, vCardType, null);
builder.addEntryHandler(this);
try {
vCardParser.parse(is, builder);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
public void onStart() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingStart();
}
}
public void onEntryCreated(VCardEntry entry) {
mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
mIndex++;
}
public void onEnd() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingEnd();
elem.verifyResolver();
}
}
}

View File

@@ -17,7 +17,10 @@
package android.pim.vcard; package android.pim.vcard;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig; import android.pim.vcard.test_utils.ContactEntry;
import android.pim.vcard.test_utils.PropertyNodesVerifierElem;
import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.pim.vcard.test_utils.VCardTestsBase;
import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -31,8 +34,6 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.CommonDataKinds.Website;
import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays; import java.util.Arrays;
/** /**
@@ -56,7 +57,6 @@ public class VCardExporterTests extends VCardTestsBase {
} }
private void testStructuredNameBasic(int vcardType) { private void testStructuredNameBasic(int vcardType) {
final boolean isV30 = VCardConfig.isV30(vcardType);
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
@@ -64,28 +64,15 @@ public class VCardExporterTests extends VCardTestsBase {
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix") .put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix") .put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") .put(StructuredName.DISPLAY_NAME, "DISPLAY NAME");
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", .addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix", + "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
.addExpectedNodeWithOrder("FN", .addExpectedNodeWithOrder("FN", "DISPLAY NAME");
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
if (isV30) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ "AppropriatePhoneticFamily");
}
} }
public void testStructuredNameBasicV21() { public void testStructuredNameBasicV21() {
@@ -96,6 +83,10 @@ public class VCardExporterTests extends VCardTestsBase {
testStructuredNameBasic(V30); testStructuredNameBasic(V30);
} }
public void testStructuredNameBasicV40() {
testStructuredNameBasic(V40);
}
/** /**
* Test that only "primary" StructuredName is emitted, so that our vCard file * Test that only "primary" StructuredName is emitted, so that our vCard file
* will not confuse the external importer, assuming there may be some importer * will not confuse the external importer, assuming there may be some importer
@@ -103,18 +94,15 @@ public class VCardExporterTests extends VCardTestsBase {
* Note that more than one "N", "FN", etc. properties are acceptable in vCard spec. * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
*/ */
private void testStructuredNameUsePrimaryCommon(int vcardType) { private void testStructuredNameUsePrimaryCommon(int vcardType) {
final boolean isV30 = (vcardType == V30);
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); final ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1") .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
.put(StructuredName.PREFIX, "DoNotEmitPrefix1") .put(StructuredName.PREFIX, "DoNotEmitPrefix1")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix1") .put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1") .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName1");
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
// With "IS_PRIMARY=1". This is what we should use. // With "IS_PRIMARY=1". This is what we should use.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
@@ -123,41 +111,30 @@ public class VCardExporterTests extends VCardTestsBase {
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix") .put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix") .put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") .put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName")
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
.put(StructuredName.IS_PRIMARY, 1); .put(StructuredName.IS_PRIMARY, 1);
// With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first. // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first.
// vCard 2.1 does not specify anything about the number of N properties. We choose not
// emitting this property.
// vCard 3.0 does (There must be one N property)
// vCard 4.0 (rev13) does (cardinality (0, 1)).
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2") .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2") .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
.put(StructuredName.PREFIX, "DoNotEmitPrefix2") .put(StructuredName.PREFIX, "DoNotEmitPrefix2")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix2") .put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
.put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2") .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName2")
.put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
.put(StructuredName.IS_PRIMARY, 1); .put(StructuredName.IS_PRIMARY, 1);
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", .addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix", + "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
.addExpectedNodeWithOrder("FN", .addExpectedNodeWithOrder("FN", "AppropriateDisplayName");
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
if (isV30) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle "
+ "AppropriatePhoneticFamily");
}
} }
public void testStructuredNameUsePrimaryV21() { public void testStructuredNameUsePrimaryV21() {
@@ -168,14 +145,152 @@ public class VCardExporterTests extends VCardTestsBase {
testStructuredNameUsePrimaryCommon(V30); testStructuredNameUsePrimaryCommon(V30);
} }
public void testStructuredNameUsePrimaryV40() {
testStructuredNameUsePrimaryCommon(V40);
}
/** /**
* Tests that only "super primary" StructuredName is emitted. * Tests that only "super primary" StructuredName is emitted.
* See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}. * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
*/ */
private void testStructuredNameUseSuperPrimaryCommon(int vcardType) { private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
final boolean isV30 = (vcardType == V30);
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); final ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1")
.put(StructuredName.PREFIX, "DoNotEmitPrefix1")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix1")
.put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay1");
// With "IS_PRIMARY=1", but we should ignore this time.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2")
.put(StructuredName.PREFIX, "DoNotEmitPrefix2")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix2")
.put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay2")
.put(StructuredName.IS_PRIMARY, 1);
// With "IS_SUPER_PRIMARY=1". This is what we should use.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
.put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName")
.put(StructuredName.IS_SUPER_PRIMARY, 1);
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3")
.put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3")
.put(StructuredName.PREFIX, "DoNotEmitPrefix3")
.put(StructuredName.SUFFIX, "DoNotEmitSuffix3")
.put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay3")
.put(StructuredName.IS_PRIMARY, 1);
final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
elem.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName");
}
public void testStructuredNameUseSuperPrimaryV21() {
testStructuredNameUseSuperPrimaryCommon(V21);
}
public void testStructuredNameUseSuperPrimaryV30() {
testStructuredNameUseSuperPrimaryCommon(V30);
}
public void testStructuredNameUseSuperPrimaryV40() {
testStructuredNameUseSuperPrimaryCommon(V40);
}
/**
* Tests phonetic names field are handled correctly.
*
* vCard 2.1 does not have any field corresponding to them.
* vCard 3.0 has SORT-STRING property, which does not support multiple values inside it.
* vCard 4.0 (rev13) has SORT-AS parameter, which has three values (family, given, middle)
* inside it.
*/
private void testStructuredNamePhoneticNameCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
final ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
.put(StructuredName.GIVEN_NAME, "AppropriateGivenName")
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
.put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName")
.put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
.put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
.put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
if (VCardConfig.isVersion40(vcardType)) {
final ContentValues contentValues = new ContentValues();
contentValues.put("SORT-AS",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName");
// vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with
// either 2.1 nor 3.0.
elem.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"),
contentValues);
} else {
elem.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
if (VCardConfig.isVersion30(vcardType)) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ " AppropriatePhoneticFamily");
}
}
elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
}
public void testStructuredNamePhoneticNameV21() {
testStructuredNamePhoneticNameCommon(V21);
}
public void testStructuredNamePhoneticNameV30() {
testStructuredNamePhoneticNameCommon(V30);
}
public void testStructuredNamePhoneticNameV40() {
testStructuredNamePhoneticNameCommon(V40);
}
// TODO: need to add test cases confirming escaping, empty values, etc.
/**
* Confirms all the other sides of the handling is correctly interpreted at one time.
*
* A kind of regression test for StructuredName handling.
*/
private void testStructuredNameComplicatedCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
final ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
@@ -221,32 +336,50 @@ public class VCardExporterTests extends VCardTestsBase {
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3") .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
.put(StructuredName.IS_PRIMARY, 1); .put(StructuredName.IS_PRIMARY, 1);
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem();
.addExpectedNodeWithOrder("N", if (VCardConfig.isVersion40(vcardType)) {
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" final ContentValues contentValues = new ContentValues();
+ "AppropriatePrefix;AppropriateSuffix", contentValues.put("SORT-AS",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName");
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) // vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with
.addExpectedNodeWithOrder("FN", // either 2.1 nor 3.0.
"AppropriatePrefix AppropriateGivenName " elem.addExpectedNodeWithOrder("N",
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + "AppropriatePrefix;AppropriateSuffix",
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"),
contentValues);
if (isV30) { } else {
elem.addExpectedNode("SORT-STRING", elem.addExpectedNodeWithOrder("N",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle" "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ " AppropriatePhoneticFamily"); + "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"));
if (VCardConfig.isVersion30(vcardType)) {
elem.addExpectedNode("SORT-STRING",
"AppropriatePhoneticGiven AppropriatePhoneticMiddle"
+ " AppropriatePhoneticFamily");
}
} }
elem.addExpectedNodeWithOrder("FN",
"AppropriatePrefix AppropriateGivenName "
+ "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix")
.addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven")
.addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle")
.addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily");
} }
public void testStructuredNameUseSuperPrimaryV21() { public void testStructuredNameComplicatedV21() {
testStructuredNameUseSuperPrimaryCommon(V21); testStructuredNameComplicatedCommon(V21);
} }
public void testStructuredNameUseSuperPrimaryV30() { public void testStructuredNameComplicatedV30() {
testStructuredNameUseSuperPrimaryCommon(V30); testStructuredNameComplicatedCommon(V30);
}
public void testStructuredNameComplicatedV40() {
testStructuredNameComplicatedCommon(V40);
} }
public void testNickNameV30() { public void testNickNameV30() {
@@ -258,6 +391,15 @@ public class VCardExporterTests extends VCardTestsBase {
.addExpectedNodeWithOrder("NICKNAME", "Nicky"); .addExpectedNodeWithOrder("NICKNAME", "Nicky");
} }
public void testNickNameV40() {
mVerifier.initForExportTest(V40);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "Nicky");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNodeWithOrder("NICKNAME", "Nicky");
}
private void testPhoneBasicCommon(int vcardType) { private void testPhoneBasicCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
@@ -275,6 +417,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPhoneBasicCommon(V30); testPhoneBasicCommon(V30);
} }
public void testPhoneBasicV40() {
testPhoneBasicCommon(V40);
}
public void testPhoneRefrainFormatting() { public void testPhoneRefrainFormatting() {
mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING); mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
@@ -367,6 +513,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPhoneVariousTypeSupport(V30); testPhoneVariousTypeSupport(V30);
} }
public void testPhoneVariousTypeSupportV40() {
testPhoneVariousTypeSupport(V40);
}
/** /**
* Tests that "PREF"s are emitted appropriately. * Tests that "PREF"s are emitted appropriately.
*/ */
@@ -403,6 +553,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPhonePrefHandlingCommon(V30); testPhonePrefHandlingCommon(V30);
} }
public void testPhonePrefHandlingV40() {
testPhonePrefHandlingCommon(V40);
}
private void testMiscPhoneTypeHandling(int vcardType) { private void testMiscPhoneTypeHandling(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -438,7 +592,7 @@ public class VCardExporterTests extends VCardTestsBase {
.put(Phone.TYPE, Phone.TYPE_CUSTOM) .put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "invalid"); .put(Phone.LABEL, "invalid");
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
if (VCardConfig.isV30(vcardType)) { if (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType)) {
// vCard 3.0 accepts "invalid". Also stop using toUpper() // vCard 3.0 accepts "invalid". Also stop using toUpper()
elem.addExpectedNode("TEL", "1", new TypeSet("Modem")) elem.addExpectedNode("TEL", "1", new TypeSet("Modem"))
.addExpectedNode("TEL", "2", new TypeSet("MSG")) .addExpectedNode("TEL", "2", new TypeSet("MSG"))
@@ -468,6 +622,10 @@ public class VCardExporterTests extends VCardTestsBase {
testMiscPhoneTypeHandling(V30); testMiscPhoneTypeHandling(V30);
} }
public void testPhoneTypeHandlingV40() {
testMiscPhoneTypeHandling(V40);
}
private void testEmailBasicCommon(int vcardType) { private void testEmailBasicCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
@@ -484,6 +642,10 @@ public class VCardExporterTests extends VCardTestsBase {
testEmailBasicCommon(V30); testEmailBasicCommon(V30);
} }
public void testEmailBasicV40() {
testEmailBasicCommon(V40);
}
private void testEmailVariousTypeSupportCommon(int vcardType) { private void testEmailVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -514,6 +676,10 @@ public class VCardExporterTests extends VCardTestsBase {
testEmailVariousTypeSupportCommon(V30); testEmailVariousTypeSupportCommon(V30);
} }
public void testEmailVariousTypeSupportV40() {
testEmailVariousTypeSupportCommon(V40);
}
private void testEmailPrefHandlingCommon(int vcardType) { private void testEmailPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -538,6 +704,10 @@ public class VCardExporterTests extends VCardTestsBase {
testEmailPrefHandlingCommon(V30); testEmailPrefHandlingCommon(V30);
} }
public void testEmailPrefHandlingV40() {
testEmailPrefHandlingCommon(V40);
}
private void testPostalAddressCommon(int vcardType) { private void testPostalAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -569,6 +739,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPostalAddressCommon(V30); testPostalAddressCommon(V30);
} }
public void testPostalAddressV40() {
testPostalAddressCommon(V40);
}
private void testPostalAddressNonNeighborhood(int vcardType) { private void testPostalAddressNonNeighborhood(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -586,6 +760,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPostalAddressNonNeighborhood(V30); testPostalAddressNonNeighborhood(V30);
} }
public void testPostalAddressNonNeighborhoodV40() {
testPostalAddressNonNeighborhood(V40);
}
private void testPostalAddressNonCity(int vcardType) { private void testPostalAddressNonCity(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -603,6 +781,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPostalAddressNonCity(V30); testPostalAddressNonCity(V30);
} }
public void testPostalAddressNonCityV40() {
testPostalAddressNonCity(V40);
}
private void testPostalOnlyWithFormattedAddressCommon(int vcardType) { private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -623,6 +805,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPostalOnlyWithFormattedAddressCommon(V30); testPostalOnlyWithFormattedAddressCommon(V30);
} }
public void testPostalOnlyWithFormattedAddressV40() {
testPostalOnlyWithFormattedAddressCommon(V40);
}
/** /**
* Tests that the vCard composer honors formatted data when it is available * Tests that the vCard composer honors formatted data when it is available
* even when it is partial. * even when it is partial.
@@ -648,6 +834,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPostalWithBothStructuredAndFormattedCommon(V30); testPostalWithBothStructuredAndFormattedCommon(V30);
} }
public void testPostalWithBothStructuredAndFormattedV40() {
testPostalWithBothStructuredAndFormattedCommon(V40);
}
private void testOrganizationCommon(int vcardType) { private void testOrganizationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -685,6 +875,10 @@ public class VCardExporterTests extends VCardTestsBase {
testOrganizationCommon(V30); testOrganizationCommon(V30);
} }
public void testOrganizationV40() {
testOrganizationCommon(V40);
}
private void testImVariousTypeSupportCommon(int vcardType) { private void testImVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -737,6 +931,10 @@ public class VCardExporterTests extends VCardTestsBase {
testImVariousTypeSupportCommon(V30); testImVariousTypeSupportCommon(V30);
} }
public void testImBasicV40() {
testImVariousTypeSupportCommon(V40);
}
private void testImPrefHandlingCommon(int vcardType) { private void testImPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -762,6 +960,10 @@ public class VCardExporterTests extends VCardTestsBase {
testImPrefHandlingCommon(V30); testImPrefHandlingCommon(V30);
} }
public void testImPrefHandlingV40() {
testImPrefHandlingCommon(V40);
}
private void testWebsiteCommon(int vcardType) { private void testWebsiteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -786,6 +988,10 @@ public class VCardExporterTests extends VCardTestsBase {
testWebsiteCommon(V30); testWebsiteCommon(V30);
} }
public void testWebsiteV40() {
testWebsiteCommon(V40);
}
private String getAndroidPropValue(final String mimeType, String value, Integer type) { private String getAndroidPropValue(final String mimeType, String value, Integer type) {
return getAndroidPropValue(mimeType, value, type, null); return getAndroidPropValue(mimeType, value, type, null);
} }
@@ -839,6 +1045,10 @@ public class VCardExporterTests extends VCardTestsBase {
testEventCommon(V30); testEventCommon(V30);
} }
public void testEventV40() {
testEventCommon(V40);
}
private void testNoteCommon(int vcardType) { private void testNoteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
@@ -860,8 +1070,13 @@ public class VCardExporterTests extends VCardTestsBase {
testNoteCommon(V30); testNoteCommon(V30);
} }
public void testNoteV40() {
testNoteCommon(V40);
}
private void testPhotoCommon(int vcardType) { private void testPhotoCommon(int vcardType) {
final boolean isV30 = vcardType == V30; final boolean useB =
(VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType));
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
@@ -870,7 +1085,7 @@ public class VCardExporterTests extends VCardTestsBase {
.put(Photo.PHOTO, sPhotoByteArray); .put(Photo.PHOTO, sPhotoByteArray);
ContentValues contentValuesForPhoto = new ContentValues(); ContentValues contentValuesForPhoto = new ContentValues();
contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64")); contentValuesForPhoto.put("ENCODING", (useB ? "b" : "BASE64"));
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "PhotoTest") .addExpectedNode("FN", "PhotoTest")
.addExpectedNode("N", "PhotoTest;;;;", .addExpectedNode("N", "PhotoTest;;;;",
@@ -887,6 +1102,10 @@ public class VCardExporterTests extends VCardTestsBase {
testPhotoCommon(V30); testPhotoCommon(V30);
} }
public void testPhotoV40() {
testPhotoCommon(V40);
}
private void testRelationCommon(int vcardType) { private void testRelationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
@@ -957,18 +1176,22 @@ public class VCardExporterTests extends VCardTestsBase {
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored. .put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored. .put(StructuredName.FAMILY_NAME, "family1") // Not primary. Should be ignored.
.put(StructuredName.DISPLAY_NAME, "display");
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1) .put(StructuredName.IS_PRIMARY, 1)
.put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want. .put(StructuredName.FAMILY_NAME, "family2") // This entry is what we want.
.put(StructuredName.DISPLAY_NAME, "display");
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1) .put(StructuredName.IS_PRIMARY, 1)
.put(StructuredName.FAMILY_NAME, "family3"); .put(StructuredName.FAMILY_NAME, "family3")
.put(StructuredName.DISPLAY_NAME, "display");
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "family4"); .put(StructuredName.FAMILY_NAME, "family4")
.put(StructuredName.DISPLAY_NAME, "display");
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("N", Arrays.asList("family2", "", "", "", "")) .addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
.addExpectedNode("FN", "family2"); .addExpectedNode("FN", "display");
} }
public void testPickUpNonEmptyContentValuesV21() { public void testPickUpNonEmptyContentValuesV21() {
@@ -978,4 +1201,23 @@ public class VCardExporterTests extends VCardTestsBase {
public void testPickUpNonEmptyContentValuesV30() { public void testPickUpNonEmptyContentValuesV30() {
testPickUpNonEmptyContentValuesCommon(V30); testPickUpNonEmptyContentValuesCommon(V30);
} }
public void testPickUpNonEmptyContentValuesV40() {
testPickUpNonEmptyContentValuesCommon(V40);
}
public void testUseMultiByteTypeV30() {
mVerifier.initForExportTest(V30);
final ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "\u96FB\u8A71")
.put(Phone.NUMBER, "1");
mVerifier.addLineVerifierElem()
.addExpected("N:")
.addExpected("FN:")
.addExpected("TEL;TYPE=\u96FB\u8A71:1");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
.addExpectedNode("TEL", "1", new TypeSet("\u96FB\u8A71"));
}
} }

View File

@@ -15,11 +15,14 @@
*/ */
package android.pim.vcard; package android.pim.vcard;
import com.android.internal.R;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig; import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
@@ -27,9 +30,7 @@ import android.provider.ContactsContract.CommonDataKinds.Photo;
import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data;
import com.android.frameworks.coretests.R;
import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays; import java.util.Arrays;
@@ -405,12 +406,12 @@ public class VCardImporterTests extends VCardTestsBase {
public void testV21SimpleCase1_Parsing() { public void testV21SimpleCase1_Parsing() {
mVerifier.initForImportTest(V21, R.raw.v21_simple_1); mVerifier.initForImportTest(V21, R.raw.v21_simple_1);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElemWithoutVersion() // no "VERSION:2.1" line.
.addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", "")); .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", ""));
} }
public void testV21SimpleCase1_Type_Generic() { public void testV21SimpleCase1_Type_Generic() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1); mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_simple_1);
mVerifier.addContentValuesVerifierElem() mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE) .addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando") .put(StructuredName.FAMILY_NAME, "Ando")
@@ -419,7 +420,7 @@ public class VCardImporterTests extends VCardTestsBase {
} }
public void testV21SimpleCase1_Type_Japanese() { public void testV21SimpleCase1_Type_Japanese() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1); mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_1);
mVerifier.addContentValuesVerifierElem() mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE) .addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando") .put(StructuredName.FAMILY_NAME, "Ando")
@@ -431,7 +432,7 @@ public class VCardImporterTests extends VCardTestsBase {
} }
public void testV21SimpleCase2() { public void testV21SimpleCase2() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2); mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_2);
mVerifier.addContentValuesVerifierElem() mVerifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE) .addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.DISPLAY_NAME, "Ando Roid"); .put(StructuredName.DISPLAY_NAME, "Ando Roid");
@@ -454,7 +455,6 @@ public class VCardImporterTests extends VCardTestsBase {
public void testV21BackslashCase_Parsing() { public void testV21BackslashCase_Parsing() {
mVerifier.initForImportTest(V21, R.raw.v21_backslash); mVerifier.initForImportTest(V21, R.raw.v21_backslash);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;", .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", "")) Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
.addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\"); .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
@@ -549,11 +549,11 @@ public class VCardImporterTests extends VCardTestsBase {
public void testV21ComplicatedCase_Parsing() { public void testV21ComplicatedCase_Parsing() {
mVerifier.initForImportTest(V21, R.raw.v21_complicated); mVerifier.initForImportTest(V21, R.raw.v21_complicated);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao", .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao")) Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
.addExpectedNodeWithOrder("FN", "Joe Due") .addExpectedNodeWithOrder("FN", "Joe Due")
.addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper", .addExpectedNodeWithOrder("ORG",
"Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper",
Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper")) Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper"))
.addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!") .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!")
.addExpectedNodeWithOrder("TITLE", "Shrimp Man") .addExpectedNodeWithOrder("TITLE", "Shrimp Man")
@@ -583,7 +583,8 @@ public class VCardImporterTests extends VCardTestsBase {
.addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com", .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
new TypeSet("PREF", "INTERNET")) new TypeSet("PREF", "INTERNET"))
.addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL")) .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL"))
.addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.") .addExpectedNodeWithOrder("NOTE",
"The following note is the example from RFC 2045.")
.addExpectedNodeWithOrder("NOTE", .addExpectedNodeWithOrder("NOTE",
"Now's the time for all folk to come to the aid of their country.", "Now's the time for all folk to come to the aid of their country.",
null, null, mContentValuesForQP, null, null) null, null, mContentValuesForQP, null, null)
@@ -677,12 +678,24 @@ public class VCardImporterTests extends VCardTestsBase {
.put(Website.TYPE, Website.TYPE_HOMEPAGE); .put(Website.TYPE, Website.TYPE_HOMEPAGE);
} }
public void testInvalidMultipleLineV21() {
mVerifier.initForImportTest(V21, R.raw.v21_invalid_multiple_line);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.GIVEN_NAME, "Omega")
.put(StructuredName.DISPLAY_NAME, "Omega");
elem.addExpected(Email.CONTENT_ITEM_TYPE)
.put(Email.TYPE, Email.TYPE_CUSTOM)
.put(Email.LABEL, "INTERNET")
.put(Email.ADDRESS, "\"Omega\" <omega@example.com>");
}
public void testV30Simple_Parsing() { public void testV30Simple_Parsing() {
mVerifier.initForImportTest(V30, R.raw.v30_simple); mVerifier.initForImportTest(V30, R.raw.v30_simple);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "3.0")
.addExpectedNodeWithOrder("FN", "And Roid") .addExpectedNodeWithOrder("FN", "And Roid")
.addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", "")) .addExpectedNodeWithOrder("N", "And;Roid;;;",
Arrays.asList("And", "Roid", "", "", ""))
.addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance", .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance",
Arrays.asList("Open", "Handset", " Alliance")) Arrays.asList("Open", "Handset", " Alliance"))
.addExpectedNodeWithOrder("SORT-STRING", "android") .addExpectedNodeWithOrder("SORT-STRING", "android")
@@ -717,10 +730,8 @@ public class VCardImporterTests extends VCardTestsBase {
// Though Japanese careers append ";;;;" at the end of the value of "SOUND", // Though Japanese careers append ";;;;" at the end of the value of "SOUND",
// vCard 2.1/3.0 specification does not allow multiple values. // vCard 2.1/3.0 specification does not allow multiple values.
// Do not need to handle it as multiple values. // Do not need to handle it as multiple values.
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1);
R.raw.v21_japanese_1);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;", .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""), Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
null, mContentValuesForSJis, null, null) null, mContentValuesForSJis, null, null)
@@ -752,37 +763,35 @@ public class VCardImporterTests extends VCardTestsBase {
/** /**
* Verifies vCard with Japanese can be parsed correctly with * Verifies vCard with Japanese can be parsed correctly with
* {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}. * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}.
*/ */
public void testV21Japanese1_Type_Generic_Utf8() { public void testV21Japanese1_Type_Generic_Utf8() {
testV21Japanese1Common( testV21Japanese1Common(
R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false); R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false);
} }
/** /**
* Verifies vCard with Japanese can be parsed correctly with * Verifies vCard with Japanese can be parsed correctly with
* {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}. * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
*/ */
public void testV21Japanese1_Type_Japanese_Sjis() { public void testV21Japanese1_Type_Japanese_Sjis() {
testV21Japanese1Common( testV21Japanese1Common(
R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true); R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
} }
/** /**
* Verifies vCard with Japanese can be parsed correctly with * Verifies vCard with Japanese can be parsed correctly with
* {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}. * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}.
* since vCard 2.1 specifies the charset of each line if it contains non-Ascii. * since vCard 2.1 specifies the charset of each line if it contains non-Ascii.
*/ */
public void testV21Japanese1_Type_Japanese_Utf8() { public void testV21Japanese1_Type_Japanese_Utf8() {
testV21Japanese1Common( testV21Japanese1Common(
R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true); R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true);
} }
public void testV21Japanese2_Parsing() { public void testV21Japanese2_Parsing() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
R.raw.v21_japanese_2);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;", .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031", Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
"", "", ""), "", "", ""),
@@ -838,10 +847,8 @@ public class VCardImporterTests extends VCardTestsBase {
} }
public void testV21MultipleEntryCase_Parse() { public void testV21MultipleEntryCase_Parse() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
R.raw.v21_multiple_entry);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;", .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""), Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
null, mContentValuesForSJis, null, null) null, mContentValuesForSJis, null, null)
@@ -854,7 +861,6 @@ public class VCardImporterTests extends VCardTestsBase {
.addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL")) .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
.addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME")); .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;", .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""), Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
null, mContentValuesForSJis, null, null) null, mContentValuesForSJis, null, null)
@@ -867,7 +873,6 @@ public class VCardImporterTests extends VCardTestsBase {
.addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY")) .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
.addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL")); .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;", .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""), Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
null, mContentValuesForSJis, null, null) null, mContentValuesForSJis, null, null)
@@ -882,8 +887,7 @@ public class VCardImporterTests extends VCardTestsBase {
} }
public void testV21MultipleEntryCase() { public void testV21MultipleEntryCase() {
mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
R.raw.v21_multiple_entry);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033") .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
@@ -957,7 +961,6 @@ public class VCardImporterTests extends VCardTestsBase {
ContentValues contentValuesForValue = new ContentValues(); ContentValues contentValuesForValue = new ContentValues();
contentValuesForValue.put("VALUE", "DATE"); contentValuesForValue.put("VALUE", "DATE");
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", "")) .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
.addExpectedNodeWithOrder("FN", "Example") .addExpectedNodeWithOrder("FN", "Example")
.addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue) .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
@@ -974,6 +977,9 @@ public class VCardImporterTests extends VCardTestsBase {
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Example") .put(StructuredName.FAMILY_NAME, "Example")
.put(StructuredName.DISPLAY_NAME, "Example"); .put(StructuredName.DISPLAY_NAME, "Example");
elem.addExpected(Event.CONTENT_ITEM_TYPE)
.put(Event.TYPE, Event.TYPE_ANNIVERSARY)
.put(Event.START_DATE, "20091010");
} }
public void testTolerateInvalidCommentLikeLineV21() { public void testTolerateInvalidCommentLikeLineV21() {
@@ -988,16 +994,15 @@ public class VCardImporterTests extends VCardTestsBase {
} }
public void testPagerV30_Parse() { public void testPagerV30_Parse() {
mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); mVerifier.initForImportTest(V30, R.raw.v30_pager);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "3.0")
.addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
.addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com", .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com",
new TypeSet("WORK", "MSG", "PAGER")); new TypeSet("WORK", "MSG", "PAGER"));
} }
public void testPagerV30() { public void testPagerV30() {
mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); mVerifier.initForImportTest(V30, R.raw.v30_pager);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "F") .put(StructuredName.FAMILY_NAME, "F")
@@ -1012,7 +1017,6 @@ public class VCardImporterTests extends VCardTestsBase {
public void testMultiBytePropV30_Parse() { public void testMultiBytePropV30_Parse() {
mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("VERSION", "3.0")
.addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
.addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39")); .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39"));
} }
@@ -1030,4 +1034,72 @@ public class VCardImporterTests extends VCardTestsBase {
.put(Phone.LABEL, "\u8D39") .put(Phone.LABEL, "\u8D39")
.put(Phone.NUMBER, "1"); .put(Phone.NUMBER, "1");
} }
public void testCommaSeparatedParamsV30_Parse() {
mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""),
new TypeSet("PREF", "HOME"))
.addExpectedNodeWithOrder("TEL", "1",
new TypeSet("COMMA,SEPARATED:INSIDE.DQUOTE", "PREF"));
}
public void testSortAsV40_Parse() {
mVerifier.initForImportTest(V40, R.raw.v40_sort_as);
final ContentValues contentValuesForSortAsN = new ContentValues();
contentValuesForSortAsN.put("SORT-AS",
"\u3042\u3093\u3069\u3046;\u308D\u3044\u3069");
final ContentValues contentValuesForSortAsOrg = new ContentValues();
contentValuesForSortAsOrg.put("SORT-AS",
"\u3050\u30FC\u3050\u308B;\u3051\u3093\u3055\u304F\u3076\u3082\u3093");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9")
.addExpectedNodeWithOrder("N",
Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9", "", "", ""),
contentValuesForSortAsN)
.addExpectedNodeWithOrder("ORG",
Arrays.asList("\u30B0\u30FC\u30B0\u30EB", "\u691C\u7D22\u90E8\u9580"),
contentValuesForSortAsOrg, new TypeSet("WORK"));
}
public void testSortAsV40() {
mVerifier.initForImportTest(V40, R.raw.v40_sort_as);
final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4")
.put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9")
.put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9")
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3042\u3093\u3069\u3046")
.put(StructuredName.PHONETIC_GIVEN_NAME,
"\u308D\u3044\u3069");
elem.addExpected(Organization.CONTENT_ITEM_TYPE)
.put(Organization.TYPE, Organization.TYPE_WORK)
.put(Organization.COMPANY, "\u30B0\u30FC\u30B0\u30EB")
.put(Organization.DEPARTMENT, "\u691C\u7D22\u90E8\u9580")
.put(Organization.PHONETIC_NAME,
"\u3050\u30FC\u3050\u308B\u3051\u3093\u3055\u304F\u3076\u3082\u3093");
}
public void testIMV21_Parse() {
mVerifier.initForImportTest(V21, R.raw.v21_im);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("X-ANDROID-CUSTOM",
Arrays.asList("vnd.android.cursor.item/nickname", "Nick", "1",
"", "", "", "", "", "", "", "", "", "", "", "", "")) // 13
.addExpectedNodeWithOrder("X-GOOGLE-TALK", "hhh@gmail.com");
}
public void testIMV21() {
mVerifier.initForImportTest(V21, R.raw.v21_im);
final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "Nick")
.put(Nickname.TYPE, "1");
elem.addExpected(Im.CONTENT_ITEM_TYPE)
.put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK)
.put(Im.TYPE, Im.TYPE_HOME)
.put(Im.DATA, "hhh@gmail.com");
}
} }

View File

@@ -13,19 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig; import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
import java.util.Arrays; import java.util.Arrays;
public class VCardJapanizationTests extends VCardTestsBase { public class VCardJapanizationTests extends VCardTestsBase {
@@ -39,7 +36,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
.put(StructuredName.PREFIX, "Dr.") .put(StructuredName.PREFIX, "Dr.")
.put(StructuredName.SUFFIX, "Ph.D"); .put(StructuredName.SUFFIX, "Ph.D");
ContentValues contentValues = ContentValues contentValues =
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8); (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null);
mVerifier.addPropertyNodesVerifierElem() mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D", .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
contentValues) contentValues)
@@ -50,15 +47,15 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testNameUtf8V21() { public void testNameUtf8V21() {
testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
} }
public void testNameUtf8V30() { public void testNameUtf8V30() {
testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
} }
public void testNameShiftJis() { public void testNameShiftJis() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -80,7 +77,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
* DoCoMo phones require all name elements should be in "family name" field. * DoCoMo phones require all name elements should be in "family name" field.
*/ */
public void testNameDoCoMo() { public void testNameDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -105,8 +102,8 @@ public class VCardJapanizationTests extends VCardTestsBase {
.addExpectedNode("X-DCM-HMN-MODE", ""); .addExpectedNode("X-DCM-HMN-MODE", "");
} }
private void testPhoneticNameCommon(int vcardType) { private void testPhoneticNameCommon(int vcardType, String charset) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -114,10 +111,10 @@ public class VCardJapanizationTests extends VCardTestsBase {
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046"); .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
final ContentValues contentValues = final ContentValues contentValues =
(VCardConfig.usesShiftJis(vcardType) ? ("SHIFT_JIS".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis : (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
mContentValuesForQPAndSJis) : mContentValuesForSJis) :
(VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8)); (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060", elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
contentValues) contentValues)
@@ -126,7 +123,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
contentValues) contentValues)
.addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046", .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
contentValues); contentValues);
if (VCardConfig.isV30(vcardType)) { if (!VCardConfig.isVersion21(vcardType)) {
elem.addExpectedNode("SORT-STRING", elem.addExpectedNode("SORT-STRING",
"\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046", "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
contentValues); contentValues);
@@ -142,23 +139,23 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testPhoneticNameForJapaneseV21Utf8() { public void testPhoneticNameForJapaneseV21Utf8() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
} }
public void testPhoneticNameForJapaneseV21Sjis() { public void testPhoneticNameForJapaneseV21Sjis() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
} }
public void testPhoneticNameForJapaneseV30Utf8() { public void testPhoneticNameForJapaneseV30Utf8() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
} }
public void testPhoneticNameForJapaneseV30SJis() { public void testPhoneticNameForJapaneseV30SJis() {
testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
} }
public void testPhoneticNameForMobileV21_1() { public void testPhoneticNameForMobileV21_1() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -182,7 +179,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testPhoneticNameForMobileV21_2() { public void testPhoneticNameForMobileV21_2() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -198,8 +195,8 @@ public class VCardJapanizationTests extends VCardTestsBase {
.put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73"); .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
} }
private void testPostalAddressWithJapaneseCommon(int vcardType) { private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
mVerifier.initForExportTest(vcardType); mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107") .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
@@ -214,11 +211,11 @@ public class VCardJapanizationTests extends VCardTestsBase {
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A"); .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ? ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
(VCardConfig.isV30(vcardType) ? mContentValuesForSJis : (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
mContentValuesForQPAndSJis) : mContentValuesForSJis) :
(VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 : (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 :
mContentValuesForQPAndUtf8)); mContentValuesForUtf8));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
// LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
@@ -240,7 +237,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
} }
public void testPostalAddresswithJapaneseV21() { public void testPostalAddresswithJapaneseV21() {
testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
} }
/** /**
@@ -248,7 +245,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
* Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
*/ */
public void testPostalAdrressForDoCoMo_1() { public void testPostalAdrressForDoCoMo_1() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
@@ -276,7 +273,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testPostalAdrressForDoCoMo_2() { public void testPostalAdrressForDoCoMo_2() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
@@ -301,7 +298,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testPostalAdrressForDoCoMo_3() { public void testPostalAdrressForDoCoMo_3() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
@@ -329,7 +326,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
* Verifies the vCard exporter tolerates null TYPE. * Verifies the vCard exporter tolerates null TYPE.
*/ */
public void testPostalAdrressForDoCoMo_4() { public void testPostalAdrressForDoCoMo_4() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "1"); .put(StructuredPostal.POBOX, "1");
@@ -371,15 +368,15 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testJapanesePhoneNumberV21_1() { public void testJapanesePhoneNumberV21_1() {
testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
} }
public void testJapanesePhoneNumberV30() { public void testJapanesePhoneNumberV30() {
testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
} }
public void testJapanesePhoneNumberDoCoMo() { public void testJapanesePhoneNumberDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE) entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "0312341234") .put(Phone.NUMBER, "0312341234")
@@ -399,7 +396,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testNoteDoCoMo() { public void testNoteDoCoMo() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry(); ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Note.CONTENT_ITEM_TYPE) entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note1"); .put(Note.NOTE, "note1");
@@ -421,7 +418,7 @@ public class VCardJapanizationTests extends VCardTestsBase {
} }
public void testAndroidCustomV21() { public void testAndroidCustomV21() {
mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC);
mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE)
.put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC"); .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
mVerifier.addPropertyNodesVerifierElemWithEmptyName() mVerifier.addPropertyNodesVerifierElemWithEmptyName()

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import android.test.InstrumentationTestRunner;
import android.test.InstrumentationTestSuite;
import junit.framework.TestSuite;
/**
* Usage: adb shell am instrument -w com.android.vcard.tests/.VCardTestRunnerTestRunner
*/
public class VCardTestRunner extends InstrumentationTestRunner {
@Override
public TestSuite getAllTests() {
TestSuite suite = new InstrumentationTestSuite(this);
suite.addTestSuite(VCardUtilsTests.class);
suite.addTestSuite(VCardTestUtilsTests.class);
suite.addTestSuite(VCardImporterTests.class);
suite.addTestSuite(VCardExporterTests.class);
suite.addTestSuite(VCardJapanizationTests.class);
return suite;
}
@Override
public ClassLoader getLoader() {
return VCardTestRunner.class.getClassLoader();
}
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard;
import com.android.internal.R;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.test.AndroidTestCase;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import java.util.Arrays;
/**
* Tests confirming utilities for vCard tests work fine.
*
* Now that the foundation classes for vCard test cases became too complicated to
* rely on without testing itself.
*/
public class VCardTestUtilsTests extends AndroidTestCase {
public void testShouldFailAtPropertyNodeVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
verifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;--", // wrong
Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
.addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
public void testShouldFailAtContentValueVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash);
verifier.addContentValuesVerifierElem()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.GIVEN_NAME, "A;B\\")
.put(StructuredName.MIDDLE_NAME, "C\\;")
.put(StructuredName.PREFIX, "D")
.put(StructuredName.SUFFIX, ":E");
// DISPLAY_NAME is missing.
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
public void testShouldFailAtLineVerification() {
boolean failureDetected = false;
try {
final VCardVerifier verifier = new VCardVerifier(this);
verifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_GENERIC);
verifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\\")
.put(StructuredName.GIVEN_NAME, ";")
.put(StructuredName.MIDDLE_NAME, ",")
.put(StructuredName.PREFIX, "\n")
.put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]");
verifier.addLineVerifierElem()
.addExpected("TEL:1") // wrong
.addExpected("FN:[<{Unescaped:Asciis}>]");
verifier.verify();
} catch (AssertionFailedError e) {
failureDetected = true;
}
if (!failureDetected) {
TestCase.fail("Test case that should fail actually succeeded.");
}
}
}

View File

@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard;
import android.pim.vcard.VCardUtils; import android.text.TextUtils;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -85,31 +84,35 @@ public class VCardUtilsTests extends TestCase {
public void testToStringAvailableAsV30ParamValue() { public void testToStringAvailableAsV30ParamValue() {
// Smoke tests. // Smoke tests.
assertEquals("HOME", VCardUtils.toStringAvailableAsV30ParameValue("HOME")); assertEquals("HOME", VCardUtils.toStringAsV30ParamValue("HOME"));
assertEquals("TEL", VCardUtils.toStringAvailableAsV30ParameValue("TEL")); assertEquals("TEL", VCardUtils.toStringAsV30ParamValue("TEL"));
assertEquals("PAGER", VCardUtils.toStringAvailableAsV30ParameValue("PAGER")); assertEquals("PAGER", VCardUtils.toStringAsV30ParamValue("PAGER"));
assertEquals("\"\"", VCardUtils.toStringAvailableAsV30ParameValue("")); assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue("")));
assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(null)));
assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(" \t")));
// non-Ascii must be allowed // non-Ascii must be allowed
assertEquals("\u4E8B\u52D9\u6240", assertEquals("\u4E8B\u52D9\u6240",
VCardUtils.toStringAvailableAsV30ParameValue("\u4E8B\u52D9\u6240")); VCardUtils.toStringAsV30ParamValue("\u4E8B\u52D9\u6240"));
// Reported as bug report. // Reported as bug report.
assertEquals("\u8D39", VCardUtils.toStringAvailableAsV30ParameValue("\u8D39")); assertEquals("\u8D39", VCardUtils.toStringAsV30ParamValue("\u8D39"));
assertEquals("\"comma,separated\"", assertEquals("\"comma,separated\"",
VCardUtils.toStringAvailableAsV30ParameValue("comma,separated")); VCardUtils.toStringAsV30ParamValue("comma,separated"));
assertEquals("\"colon:aware\"", assertEquals("\"colon:aware\"",
VCardUtils.toStringAvailableAsV30ParameValue("colon:aware")); VCardUtils.toStringAsV30ParamValue("colon:aware"));
// CTL characters. // CTL characters.
assertEquals("CTLExample", assertEquals("CTLExample",
VCardUtils.toStringAvailableAsV30ParameValue("CTL\u0001Example")); VCardUtils.toStringAsV30ParamValue("CTL\u0001Example"));
assertTrue(TextUtils.isEmpty(
VCardUtils.toStringAsV30ParamValue("\u0001\u0002\u0003")));
// DQUOTE must be removed. // DQUOTE must be removed.
assertEquals("quoted", assertEquals("quoted",
VCardUtils.toStringAvailableAsV30ParameValue("\"quoted\"")); VCardUtils.toStringAsV30ParamValue("\"quoted\""));
// DQUOTE must be removed basically, but we should detect a space, which // DQUOTE must be removed basically, but we should detect a space, which
// require us to use DQUOTE again. // require us to use DQUOTE again.
// Right-side has one more illegal dquote to test quote-handle code thoroughly. // Right-side has one more illegal dquote to test quote-handle code thoroughly.
assertEquals("\"Already quoted\"", assertEquals("\"Already quoted\"",
VCardUtils.toStringAvailableAsV30ParameValue("\"Already quoted\"\"")); VCardUtils.toStringAsV30ParamValue("\"Already quoted\"\""));
} }
} }

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package android.pim.vcard.test_utils;
import android.content.ContentValues;
import android.provider.ContactsContract.Data;
import java.util.ArrayList;
import java.util.List;
/**
* <p>
* The class representing one contact, which should contain multiple ContentValues like
* StructuredName, Email, etc.
* </p>
*/
public final class ContactEntry {
private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
public ContentValuesBuilder addContentValues(String mimeType) {
ContentValues contentValues = new ContentValues();
contentValues.put(Data.MIMETYPE, mimeType);
mContentValuesList.add(contentValues);
return new ContentValuesBuilder(contentValues);
}
public List<ContentValues> getList() {
return mContentValuesList;
}
}

View File

@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard.test_utils;
package android.pim.vcard;
import android.content.ContentValues; import android.content.ContentValues;
@@ -22,7 +21,7 @@ import android.content.ContentValues;
* ContentValues-like class which enables users to chain put() methods and restricts * ContentValues-like class which enables users to chain put() methods and restricts
* the other methods. * the other methods.
*/ */
/* package */ class ContentValuesBuilder { public class ContentValuesBuilder {
private final ContentValues mContentValues; private final ContentValues mContentValues;
public ContentValuesBuilder(final ContentValues contentValues) { public ContentValuesBuilder(final ContentValues contentValues) {
@@ -34,6 +33,7 @@ import android.content.ContentValues;
return this; return this;
} }
/*
public ContentValuesBuilder put(String key, Byte value) { public ContentValuesBuilder put(String key, Byte value) {
mContentValues.put(key, value); mContentValues.put(key, value);
return this; return this;
@@ -42,13 +42,14 @@ import android.content.ContentValues;
public ContentValuesBuilder put(String key, Short value) { public ContentValuesBuilder put(String key, Short value) {
mContentValues.put(key, value); mContentValues.put(key, value);
return this; return this;
} }*/
public ContentValuesBuilder put(String key, Integer value) { public ContentValuesBuilder put(String key, Integer value) {
mContentValues.put(key, value); mContentValues.put(key, value);
return this; return this;
} }
/*
public ContentValuesBuilder put(String key, Long value) { public ContentValuesBuilder put(String key, Long value) {
mContentValues.put(key, value); mContentValues.put(key, value);
return this; return this;
@@ -67,7 +68,7 @@ import android.content.ContentValues;
public ContentValuesBuilder put(String key, Boolean value) { public ContentValuesBuilder put(String key, Boolean value) {
mContentValues.put(key, value); mContentValues.put(key, value);
return this; return this;
} }*/
public ContentValuesBuilder put(String key, byte[] value) { public ContentValuesBuilder put(String key, byte[] value) {
mContentValues.put(key, value); mContentValues.put(key, value);

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard.test_utils;
import android.pim.vcard.VCardEntry;
import android.pim.vcard.VCardEntryHandler;
import android.test.AndroidTestCase;
import java.util.ArrayList;
import java.util.List;
public class ContentValuesVerifier implements VCardEntryHandler {
private List<ContentValuesVerifierElem> mContentValuesVerifierElemList =
new ArrayList<ContentValuesVerifierElem>();
private int mIndex;
public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) {
ContentValuesVerifierElem elem = new ContentValuesVerifierElem(androidTestCase);
mContentValuesVerifierElemList.add(elem);
return elem;
}
public void onStart() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingStart();
}
}
public void onEntryCreated(VCardEntry entry) {
AndroidTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size());
mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry);
mIndex++;
}
public void onEnd() {
for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) {
elem.onParsingEnd();
elem.verifyResolver();
}
}
}

View File

@@ -13,17 +13,15 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardEntry; import android.pim.vcard.VCardEntry;
import android.pim.vcard.VCardEntryCommitter; import android.pim.vcard.VCardEntryCommitter;
import android.pim.vcard.VCardEntryConstructor; import android.pim.vcard.VCardEntryConstructor;
import android.pim.vcard.VCardEntryHandler; import android.pim.vcard.VCardEntryHandler;
import android.pim.vcard.VCardParser; import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21; import android.pim.vcard.VCardUtils;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException; import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
@@ -31,7 +29,7 @@ import android.test.AndroidTestCase;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
/* package */ class ContentValuesVerifierElem { public class ContentValuesVerifierElem {
private final AndroidTestCase mTestCase; private final AndroidTestCase mTestCase;
private final ImportTestResolver mResolver; private final ImportTestResolver mResolver;
private final VCardEntryHandler mHandler; private final VCardEntryHandler mHandler;
@@ -49,20 +47,14 @@ import java.io.InputStream;
return new ContentValuesBuilder(contentValues); return new ContentValuesBuilder(contentValues);
} }
public void verify(int resId, int vCardType) public void verify(int resId, int vcardType)
throws IOException, VCardException { throws IOException, VCardException {
verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType); verify(mTestCase.getContext().getResources().openRawResource(resId), vcardType);
} }
public void verify(InputStream is, int vCardType) throws IOException, VCardException { public void verify(InputStream is, int vcardType) throws IOException, VCardException {
final VCardParser vCardParser; final VCardParser vCardParser = VCardUtils.getAppropriateParser(vcardType);
if (VCardConfig.isV30(vCardType)) { final VCardEntryConstructor builder = new VCardEntryConstructor(vcardType, null);
vCardParser = new VCardParser_V30(true); // use StrictParsing
} else {
vCardParser = new VCardParser_V21();
}
VCardEntryConstructor builder =
new VCardEntryConstructor(null, null, false, vCardType, null);
builder.addEntryHandler(mHandler); builder.addEntryHandler(mHandler);
try { try {
vCardParser.parse(is, builder); vCardParser.parse(is, builder);

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009 The Android Open Source Project * Copyright (C) 2010 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not * 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 * use this file except in compliance with the License. You may obtain a copy of
@@ -13,7 +13,7 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
@@ -21,11 +21,12 @@ import android.content.Entity;
import android.content.EntityIterator; import android.content.EntityIterator;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.pim.vcard.VCardComposer;
import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentProvider; import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.mock.MockCursor; import android.test.mock.MockCursor;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -34,82 +35,44 @@ import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
/* package */ public class ExportTestResolver extends MockContentResolver { public class ExportTestProvider extends MockContentProvider {
ExportTestProvider mProvider;
public ExportTestResolver(TestCase testCase) {
mProvider = new ExportTestProvider(testCase);
addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
}
public ContactEntry addInputContactEntry() {
return mProvider.buildInputEntry();
}
public ExportTestProvider getProvider() {
return mProvider;
}
}
/* package */ class MockEntityIterator implements EntityIterator {
List<Entity> mEntityList;
Iterator<Entity> mIterator;
public MockEntityIterator(List<ContentValues> contentValuesList) {
mEntityList = new ArrayList<Entity>();
Entity entity = new Entity(new ContentValues());
for (ContentValues contentValues : contentValuesList) {
entity.addSubValue(Data.CONTENT_URI, contentValues);
}
mEntityList.add(entity);
mIterator = mEntityList.iterator();
}
public boolean hasNext() {
return mIterator.hasNext();
}
public Entity next() {
return mIterator.next();
}
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
public void reset() {
mIterator = mEntityList.iterator();
}
public void close() {
}
}
/**
* Represents one contact, which should contain multiple ContentValues like
* StructuredName, Email, etc.
*/
/* package */ class ContactEntry {
private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>();
public ContentValuesBuilder addContentValues(String mimeType) {
ContentValues contentValues = new ContentValues();
contentValues.put(Data.MIMETYPE, mimeType);
mContentValuesList.add(contentValues);
return new ContentValuesBuilder(contentValues);
}
public List<ContentValues> getList() {
return mContentValuesList;
}
}
/* package */ class ExportTestProvider extends MockContentProvider {
final private TestCase mTestCase;
final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
public ExportTestProvider(TestCase testCase) { private static class MockEntityIterator implements EntityIterator {
mTestCase = testCase; List<Entity> mEntityList;
Iterator<Entity> mIterator;
public MockEntityIterator(List<ContentValues> contentValuesList) {
mEntityList = new ArrayList<Entity>();
Entity entity = new Entity(new ContentValues());
for (ContentValues contentValues : contentValuesList) {
entity.addSubValue(Data.CONTENT_URI, contentValues);
}
mEntityList.add(entity);
mIterator = mEntityList.iterator();
}
public boolean hasNext() {
return mIterator.hasNext();
}
public Entity next() {
return mIterator.next();
}
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
public void reset() {
mIterator = mEntityList.iterator();
}
public void close() {
}
}
public ExportTestProvider(AndroidTestCase androidTestCase) {
} }
public ContactEntry buildInputEntry() { public ContactEntry buildInputEntry() {
@@ -126,27 +89,17 @@ import java.util.List;
* We still keep using this method since we don't have a propeer way to know * We still keep using this method since we don't have a propeer way to know
* which value in the ContentValue corresponds to the entry in Contacts database. * which value in the ContentValue corresponds to the entry in Contacts database.
* </p> * </p>
* <p>
* Detail:
* There's an easy way to know which index "family name" corresponds to, via
* {@link android.provider.ContactsContract}.
* FAMILY_NAME equals DATA3, so the corresponding index
* for "family name" should be 2 (note that index is 0-origin).
* However, we cannot know what the index 2 corresponds to; it may be "family name",
* "label" for now, but may be the other some column in the future. We don't have
* convenient way to know the original data structure.
* </p>
*/ */
public EntityIterator queryEntities(Uri uri, public EntityIterator queryEntities(Uri uri,
String selection, String[] selectionArgs, String sortOrder) { String selection, String[] selectionArgs, String sortOrder) {
mTestCase.assertTrue(uri != null); TestCase.assertTrue(uri != null);
mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())); TestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
final String authority = uri.getAuthority(); final String authority = uri.getAuthority();
mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority)); TestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection)); TestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
mTestCase.assertEquals(1, selectionArgs.length); TestCase.assertEquals(1, selectionArgs.length);
final int id = Integer.parseInt(selectionArgs[0]); final int id = Integer.parseInt(selectionArgs[0]);
mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size()); TestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
return new MockEntityIterator(mContactEntryList.get(id).getList()); return new MockEntityIterator(mContactEntryList.get(id).getList());
} }
@@ -154,11 +107,11 @@ import java.util.List;
@Override @Override
public Cursor query(Uri uri,String[] projection, public Cursor query(Uri uri,String[] projection,
String selection, String[] selectionArgs, String sortOrder) { String selection, String[] selectionArgs, String sortOrder) {
mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri)); TestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
// In this test, following arguments are not supported. // In this test, following arguments are not supported.
mTestCase.assertNull(selection); TestCase.assertNull(selection);
mTestCase.assertNull(selectionArgs); TestCase.assertNull(selectionArgs);
mTestCase.assertNull(sortOrder); TestCase.assertNull(sortOrder);
return new MockCursor() { return new MockCursor() {
int mCurrentPosition = -1; int mCurrentPosition = -1;
@@ -196,14 +149,14 @@ import java.util.List;
@Override @Override
public int getColumnIndex(String columnName) { public int getColumnIndex(String columnName) {
mTestCase.assertEquals(Contacts._ID, columnName); TestCase.assertEquals(Contacts._ID, columnName);
return 0; return 0;
} }
@Override @Override
public int getInt(int columnIndex) { public int getInt(int columnIndex) {
mTestCase.assertEquals(0, columnIndex); TestCase.assertEquals(0, columnIndex);
mTestCase.assertTrue(mCurrentPosition >= 0 TestCase.assertTrue(mCurrentPosition >= 0
&& mCurrentPosition < mContactEntryList.size()); && mCurrentPosition < mContactEntryList.size());
return mCurrentPosition; return mCurrentPosition;
} }
@@ -218,4 +171,4 @@ import java.util.List;
} }
}; };
} }
} }

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package android.pim.vcard.test_utils;
import android.pim.vcard.VCardComposer;
import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
public class ExportTestResolver extends MockContentResolver {
private final ExportTestProvider mProvider;
public ExportTestResolver(AndroidTestCase androidTestCase) {
mProvider = new ExportTestProvider(androidTestCase);
addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider);
addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider);
}
public ContactEntry addInputContactEntry() {
return mProvider.buildInputEntry();
}
public ExportTestProvider getProvider() {
return mProvider;
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009 The Android Open Source Project * Copyright (C) 2010 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentProviderOperation; import android.content.ContentProviderOperation;
import android.content.ContentProviderResult; import android.content.ContentProviderResult;
@@ -34,8 +34,8 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website; import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentProvider; import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.text.TextUtils; import android.text.TextUtils;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -51,38 +51,7 @@ import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
/* package */ class ImportTestResolver extends MockContentResolver { public class ImportTestProvider extends MockContentProvider {
final ImportTestProvider mProvider;
public ImportTestResolver(TestCase testCase) {
mProvider = new ImportTestProvider(testCase);
}
@Override
public ContentProviderResult[] applyBatch(String authority,
ArrayList<ContentProviderOperation> operations) {
equalsString(authority, RawContacts.CONTENT_URI.toString());
return mProvider.applyBatch(operations);
}
public void addExpectedContentValues(ContentValues expectedContentValues) {
mProvider.addExpectedContentValues(expectedContentValues);
}
public void verify() {
mProvider.verify();
}
private static boolean equalsString(String a, String b) {
if (a == null || a.length() == 0) {
return b == null || b.length() == 0;
} else {
return a.equals(b);
}
}
}
/* package */ class ImportTestProvider extends MockContentProvider {
private static final Set<String> sKnownMimeTypeSet = private static final Set<String> sKnownMimeTypeSet =
new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
@@ -95,10 +64,7 @@ import java.util.TreeMap;
final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
private final TestCase mTestCase; public ImportTestProvider(AndroidTestCase androidTestCase) {
public ImportTestProvider(TestCase testCase) {
mTestCase = testCase;
mMimeTypeToExpectedContentValues = mMimeTypeToExpectedContentValues =
new HashMap<String, Collection<ContentValues>>(); new HashMap<String, Collection<ContentValues>>();
for (String acceptanbleMimeType : sKnownMimeTypeSet) { for (String acceptanbleMimeType : sKnownMimeTypeSet) {
@@ -113,7 +79,7 @@ import java.util.TreeMap;
public void addExpectedContentValues(ContentValues expectedContentValues) { public void addExpectedContentValues(ContentValues expectedContentValues) {
final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE); final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE);
if (!sKnownMimeTypeSet.contains(mimeType)) { if (!sKnownMimeTypeSet.contains(mimeType)) {
mTestCase.fail(String.format( TestCase.fail(String.format(
"Unknow MimeType %s in the test code. Test code should be broken.", "Unknow MimeType %s in the test code. Test code should be broken.",
mimeType)); mimeType));
} }
@@ -127,7 +93,7 @@ import java.util.TreeMap;
public ContentProviderResult[] applyBatch( public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations) { ArrayList<ContentProviderOperation> operations) {
if (operations == null) { if (operations == null) {
mTestCase.fail("There is no operation."); TestCase.fail("There is no operation.");
} }
final int size = operations.size(); final int size = operations.size();
@@ -148,12 +114,12 @@ import java.util.TreeMap;
fakeResultArray, i); fakeResultArray, i);
final Uri uri = operation.getUri(); final Uri uri = operation.getUri();
if (uri.equals(RawContacts.CONTENT_URI)) { if (uri.equals(RawContacts.CONTENT_URI)) {
mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
} else if (uri.equals(Data.CONTENT_URI)) { } else if (uri.equals(Data.CONTENT_URI)) {
final String mimeType = actualContentValues.getAsString(Data.MIMETYPE); final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
if (!sKnownMimeTypeSet.contains(mimeType)) { if (!sKnownMimeTypeSet.contains(mimeType)) {
mTestCase.fail(String.format( TestCase.fail(String.format(
"Unknown MimeType %s. Probably added after developing this test", "Unknown MimeType %s. Probably added after developing this test",
mimeType)); mimeType));
} }
@@ -185,7 +151,7 @@ import java.util.TreeMap;
final Collection<ContentValues> contentValuesCollection = final Collection<ContentValues> contentValuesCollection =
mMimeTypeToExpectedContentValues.get(mimeType); mMimeTypeToExpectedContentValues.get(mimeType);
if (contentValuesCollection.isEmpty()) { if (contentValuesCollection.isEmpty()) {
mTestCase.fail("ContentValues for MimeType " + mimeType TestCase.fail("ContentValues for MimeType " + mimeType
+ " is not expected at all (" + actualContentValues + ")"); + " is not expected at all (" + actualContentValues + ")");
} }
boolean checked = false; boolean checked = false;
@@ -197,23 +163,25 @@ import java.util.TreeMap;
+ convertToEasilyReadableString(actualContentValues));*/ + convertToEasilyReadableString(actualContentValues));*/
if (equalsForContentValues(expectedContentValues, if (equalsForContentValues(expectedContentValues,
actualContentValues)) { actualContentValues)) {
mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
checked = true; checked = true;
break; break;
} }
} }
if (!checked) { if (!checked) {
final StringBuilder builder = new StringBuilder(); final StringBuilder builder = new StringBuilder();
builder.append("\n");
builder.append("Unexpected: "); builder.append("Unexpected: ");
builder.append(convertToEasilyReadableString(actualContentValues)); builder.append(convertToEasilyReadableString(actualContentValues));
builder.append("\nExpected: "); builder.append("\n");
builder.append("Expected : ");
for (ContentValues expectedContentValues : contentValuesCollection) { for (ContentValues expectedContentValues : contentValuesCollection) {
builder.append(convertToEasilyReadableString(expectedContentValues)); builder.append(convertToEasilyReadableString(expectedContentValues));
} }
mTestCase.fail(builder.toString()); TestCase.fail(builder.toString());
} }
} else { } else {
mTestCase.fail("Unexpected Uri has come: " + uri); TestCase.fail("Unexpected Uri has come: " + uri);
} }
} // for (int i = 0; i < size; i++) { } // for (int i = 0; i < size; i++) {
return fakeResultArray; return fakeResultArray;
@@ -232,7 +200,7 @@ import java.util.TreeMap;
final String failMsg = final String failMsg =
"There is(are) remaining expected ContentValues instance(s): \n" "There is(are) remaining expected ContentValues instance(s): \n"
+ builder.toString(); + builder.toString();
mTestCase.fail(failMsg); TestCase.fail(failMsg);
} }
} }
@@ -252,7 +220,7 @@ import java.util.TreeMap;
if (Data.MIMETYPE.equals(key)) { if (Data.MIMETYPE.equals(key)) {
mimeTypeValue = valueString; mimeTypeValue = valueString;
} else { } else {
mTestCase.assertNotNull(key); TestCase.assertNotNull(key);
sortedMap.put(key, valueString); sortedMap.put(key, valueString);
} }
} }
@@ -273,7 +241,7 @@ import java.util.TreeMap;
} }
private static boolean equalsForContentValues( private static boolean equalsForContentValues(
ContentValues expected, ContentValues actual) { final ContentValues expected, final ContentValues actual) {
if (expected == actual) { if (expected == actual) {
return true; return true;
} else if (expected == null || actual == null || expected.size() != actual.size()) { } else if (expected == null || actual == null || expected.size() != actual.size()) {
@@ -286,15 +254,21 @@ import java.util.TreeMap;
if (!actual.containsKey(key)) { if (!actual.containsKey(key)) {
return false; return false;
} }
// Type mismatch usuall happens as importer doesn't care the type of each value.
// For example, variable type might be Integer when importing the type of TEL,
// while variable type would be String when importing the type of RELATION.
final Object actualValue = actual.get(key);
if (value instanceof byte[]) { if (value instanceof byte[]) {
Object actualValue = actual.get(key);
if (!Arrays.equals((byte[])value, (byte[])actualValue)) { if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
byte[] e = (byte[])value;
byte[] a = (byte[])actualValue;
return false; return false;
} }
} else if (!value.equals(actual.get(key))) { } else if (!value.equals(actualValue) &&
return false; !value.toString().equals(actualValue.toString())) {
return false;
} }
} }
return true; return true;
} }
} }

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard.test_utils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentValues;
import android.provider.ContactsContract.RawContacts;
import android.test.AndroidTestCase;
import android.test.mock.MockContentResolver;
import java.util.ArrayList;
public class ImportTestResolver extends MockContentResolver {
private final ImportTestProvider mProvider;
public ImportTestResolver(AndroidTestCase androidTestCase) {
mProvider = new ImportTestProvider(androidTestCase);
}
@Override
public ContentProviderResult[] applyBatch(String authority,
ArrayList<ContentProviderOperation> operations) {
equalsString(authority, RawContacts.CONTENT_URI.toString());
return mProvider.applyBatch(operations);
}
public void addExpectedContentValues(ContentValues expectedContentValues) {
mProvider.addExpectedContentValues(expectedContentValues);
}
public void verify() {
mProvider.verify();
}
private static boolean equalsString(String a, String b) {
if (a == null || a.length() == 0) {
return b == null || b.length() == 0;
} else {
return a.equals(b);
}
}
}

View File

@@ -13,39 +13,40 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.Context; import android.content.Context;
import android.pim.vcard.VCardComposer; import android.pim.vcard.VCardComposer;
import android.test.AndroidTestCase;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.util.ArrayList; import java.util.ArrayList;
class LineVerifier implements VCardComposer.OneEntryHandler { public class LineVerifier implements VCardComposer.OneEntryHandler {
private final TestCase mTestCase; private final AndroidTestCase mAndroidTestCase;
private final ArrayList<LineVerifierElem> mLineVerifierElemList; private final ArrayList<LineVerifierElem> mLineVerifierElemList;
private int mVCardType; private int mVCardType;
private int index; private int index;
public LineVerifier(TestCase testCase, int vcardType) { public LineVerifier(AndroidTestCase androidTestCase, int vcardType) {
mTestCase = testCase; mAndroidTestCase = androidTestCase;
mLineVerifierElemList = new ArrayList<LineVerifierElem>(); mLineVerifierElemList = new ArrayList<LineVerifierElem>();
mVCardType = vcardType; mVCardType = vcardType;
} }
public LineVerifierElem addLineVerifierElem() { public LineVerifierElem addLineVerifierElem() {
LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType); LineVerifierElem lineVerifier = new LineVerifierElem(mAndroidTestCase, mVCardType);
mLineVerifierElemList.add(lineVerifier); mLineVerifierElemList.add(lineVerifier);
return lineVerifier; return lineVerifier;
} }
public void verify(String vcard) { public void verify(String vcard) {
if (index >= mLineVerifierElemList.size()) { if (index >= mLineVerifierElemList.size()) {
mTestCase.fail("Insufficient number of LineVerifier (" + index + ")"); TestCase.fail("Insufficient number of LineVerifier (" + index + ")");
} }
LineVerifierElem lineVerifier = mLineVerifierElemList.get(index); final LineVerifierElem lineVerifier = mLineVerifierElemList.get(index);
lineVerifier.verify(vcard); lineVerifier.verify(vcard);
index++; index++;

View File

@@ -13,9 +13,10 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.pim.vcard.VCardConfig; import android.pim.vcard.VCardConfig;
import android.test.AndroidTestCase;
import android.text.TextUtils; import android.text.TextUtils;
import junit.framework.TestCase; import junit.framework.TestCase;
@@ -23,14 +24,12 @@ import junit.framework.TestCase;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
class LineVerifierElem { public class LineVerifierElem {
private final TestCase mTestCase;
private final List<String> mExpectedLineList = new ArrayList<String>(); private final List<String> mExpectedLineList = new ArrayList<String>();
private final boolean mIsV30; private final int mVCardType;
public LineVerifierElem(TestCase testCase, int vcardType) { public LineVerifierElem(AndroidTestCase androidTestCase, int vcardType) {
mTestCase = testCase; mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
} }
public LineVerifierElem addExpected(final String line) { public LineVerifierElem addExpected(final String line) {
@@ -55,21 +54,23 @@ class LineVerifierElem {
if ("BEGIN:VCARD".equalsIgnoreCase(line)) { if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
if (beginExists) { if (beginExists) {
mTestCase.fail("Multiple \"BEGIN:VCARD\" line found"); TestCase.fail("Multiple \"BEGIN:VCARD\" line found");
} else { } else {
beginExists = true; beginExists = true;
continue; continue;
} }
} else if ("END:VCARD".equalsIgnoreCase(line)) { } else if ("END:VCARD".equalsIgnoreCase(line)) {
if (endExists) { if (endExists) {
mTestCase.fail("Multiple \"END:VCARD\" line found"); TestCase.fail("Multiple \"END:VCARD\" line found");
} else { } else {
endExists = true; endExists = true;
continue; continue;
} }
} else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) { } else if ((VCardConfig.isVersion21(mVCardType) ? "VERSION:2.1" :
(VCardConfig.isVersion30(mVCardType) ? "VERSION:3.0" :
"VERSION:4.0")).equalsIgnoreCase(line)) {
if (versionExists) { if (versionExists) {
mTestCase.fail("Multiple VERSION line + found"); TestCase.fail("Multiple VERSION line + found");
} else { } else {
versionExists = true; versionExists = true;
continue; continue;
@@ -77,18 +78,16 @@ class LineVerifierElem {
} }
if (!beginExists) { if (!beginExists) {
mTestCase.fail("Property other than BEGIN came before BEGIN property: " TestCase.fail("Property other than BEGIN came before BEGIN property: " + line);
+ line);
} else if (endExists) { } else if (endExists) {
mTestCase.fail("Property other than END came after END property: " TestCase.fail("Property other than END came after END property: " + line);
+ line);
} }
final int index = mExpectedLineList.indexOf(line); final int index = mExpectedLineList.indexOf(line);
if (index >= 0) { if (index >= 0) {
mExpectedLineList.remove(index); mExpectedLineList.remove(index);
} else { } else {
mTestCase.fail("Unexpected line: " + line); TestCase.fail("Unexpected line: " + line);
} }
} }
@@ -99,7 +98,7 @@ class LineVerifierElem {
buffer.append("\n"); buffer.append("\n");
} }
mTestCase.fail("Expected line(s) not found:" + buffer.toString()); TestCase.fail("Expected line(s) not found:" + buffer.toString());
} }
} }
} }

View File

@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardEntry; import android.pim.vcard.VCardEntry;
import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -26,12 +25,18 @@ import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
* <p>
* The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix").
* </p>
* <p>
* Previously used in main vCard handling code but now exists only for testing. * Previously used in main vCard handling code but now exists only for testing.
* * </p>
* <p>
* Especially useful for testing parser code (VCardParser), since all properties can be * Especially useful for testing parser code (VCardParser), since all properties can be
* checked via this class unlike {@link VCardEntry}, which only emits the result of * checked via this class unlike {@link VCardEntry}, which only emits the result of
* interpretation of the content of each vCard. We cannot know whether vCard parser or * interpretation of the content of each vCard. We cannot know whether vCard parser or
* ContactStruct is wrong withouth this class. * {@link VCardEntry} is wrong without this class.
* </p>
*/ */
public class PropertyNode { public class PropertyNode {
public String propName; public String propName;
@@ -43,7 +48,8 @@ public class PropertyNode {
*/ */
public byte[] propValue_bytes; public byte[] propValue_bytes;
/** param store: key=paramType, value=paramValue /**
* param store: key=paramType, value=paramValue
* Note that currently PropertyNode class does not support multiple param-values * Note that currently PropertyNode class does not support multiple param-values
* defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
* one String value like "A,B", not ["A", "B"]... * one String value like "A,B", not ["A", "B"]...

View File

@@ -0,0 +1,82 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.pim.vcard.test_utils;
import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardUtils;
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
public class PropertyNodesVerifier extends VNodeBuilder {
private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
private final AndroidTestCase mAndroidTestCase;
private int mIndex;
public PropertyNodesVerifier(AndroidTestCase testCase) {
super();
mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
mAndroidTestCase = testCase;
}
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
mPropertyNodesVerifierElemList.add(elem);
return elem;
}
public void verify(int resId, int vcardType) throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vcardType);
}
public void verify(int resId, int vcardType, final VCardParser parser)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
vcardType, parser);
}
public void verify(InputStream is, int vcardType) throws IOException, VCardException {
final VCardParser parser = VCardUtils.getAppropriateParser(vcardType);
verify(is, vcardType, parser);
}
public void verify(InputStream is, int vcardType, final VCardParser parser)
throws IOException, VCardException {
try {
parser.parse(is, this);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
@Override
public void endEntry() {
super.endEntry();
AndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
AndroidTestCase.assertTrue(mIndex < vNodeList.size());
mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
mIndex++;
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2009 The Android Open Source Project * Copyright (C) 2010 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -13,87 +13,19 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
/* package */ class PropertyNodesVerifier extends VNodeBuilder {
private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList;
private final AndroidTestCase mAndroidTestCase;
private int mIndex;
public PropertyNodesVerifier(AndroidTestCase testCase) {
mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>();
mAndroidTestCase = testCase;
}
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase);
mPropertyNodesVerifierElemList.add(elem);
return elem;
}
public void verify(int resId, int vCardType)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType);
}
public void verify(int resId, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
verify(mAndroidTestCase.getContext().getResources().openRawResource(resId),
vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType) throws IOException, VCardException {
final VCardParser vCardParser;
if (VCardConfig.isV30(vCardType)) {
vCardParser = new VCardParser_V30(true); // Use StrictParsing.
} else {
vCardParser = new VCardParser_V21();
}
verify(is, vCardType, vCardParser);
}
public void verify(InputStream is, int vCardType, final VCardParser vCardParser)
throws IOException, VCardException {
try {
vCardParser.parse(is, this);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
}
}
}
}
@Override
public void endEntry() {
super.endEntry();
mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size());
mAndroidTestCase.assertTrue(mIndex < vNodeList.size());
mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex));
mIndex++;
}
}
/** /**
* Utility class which verifies input VNode. * Utility class which verifies input VNode.
* *
@@ -102,7 +34,7 @@ import java.util.List;
* If the node does not exist in the "ordered list", the class refers to * If the node does not exist in the "ordered list", the class refers to
* "unorderd expected property set" and checks the node is expected somewhere. * "unorderd expected property set" and checks the node is expected somewhere.
*/ */
/* package */ class PropertyNodesVerifierElem { public class PropertyNodesVerifierElem {
public static class TypeSet extends HashSet<String> { public static class TypeSet extends HashSet<String> {
public TypeSet(String ... array) { public TypeSet(String ... array) {
super(Arrays.asList(array)); super(Arrays.asList(array));
@@ -119,12 +51,10 @@ import java.util.List;
// Intentionally use ArrayList instead of Set, assuming there may be more than one // Intentionally use ArrayList instead of Set, assuming there may be more than one
// exactly same objects. // exactly same objects.
private final ArrayList<PropertyNode> mUnorderedNodeList; private final ArrayList<PropertyNode> mUnorderedNodeList;
private final TestCase mTestCase;
public PropertyNodesVerifierElem(TestCase testCase) { public PropertyNodesVerifierElem(AndroidTestCase androidTestCase) {
mOrderedNodeMap = new HashMap<String, List<PropertyNode>>(); mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
mUnorderedNodeList = new ArrayList<PropertyNode>(); mUnorderedNodeList = new ArrayList<PropertyNode>();
mTestCase = testCase;
} }
// WithOrder // WithOrder
@@ -170,6 +100,12 @@ import java.util.List;
paramMap_TYPE, null); paramMap_TYPE, null);
} }
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName,
List<String> propValueList, ContentValues paramMap, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, null, propValueList, null, paramMap,
paramMap_TYPE, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
ContentValues paramMap, TypeSet paramMap_TYPE) { ContentValues paramMap, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, propValue, null, null, return addExpectedNodeWithOrder(propName, propValue, null, null,
@@ -182,13 +118,19 @@ import java.util.List;
paramMap_TYPE, null); paramMap_TYPE, null);
} }
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
List<String> propValueList, ContentValues paramMap) {
return addExpectedNodeWithOrder(propName, propValue, propValueList, null, paramMap,
null, null);
}
public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue,
List<String> propValueList, byte[] propValue_bytes, List<String> propValueList, byte[] propValue_bytes,
ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) { ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
if (propValue == null && propValueList != null) { if (propValue == null && propValueList != null) {
propValue = concatinateListWithSemiColon(propValueList); propValue = concatinateListWithSemiColon(propValueList);
} }
PropertyNode propertyNode = new PropertyNode(propName, final PropertyNode propertyNode = new PropertyNode(propName,
propValue, propValueList, propValue_bytes, propValue, propValueList, propValue_bytes,
paramMap, paramMap_TYPE, propGroupSet); paramMap, paramMap_TYPE, propGroupSet);
List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
@@ -267,8 +209,9 @@ import java.util.List;
for (PropertyNode actualNode : vnode.propList) { for (PropertyNode actualNode : vnode.propList) {
verifyNode(actualNode.propName, actualNode); verifyNode(actualNode.propName, actualNode);
} }
if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) { if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) {
List<String> expectedProps = new ArrayList<String>(); final List<String> expectedProps = new ArrayList<String>();
for (List<PropertyNode> nodes : mOrderedNodeMap.values()) { for (List<PropertyNode> nodes : mOrderedNodeMap.values()) {
for (PropertyNode node : nodes) { for (PropertyNode node : nodes) {
if (!expectedProps.contains(node.propName)) { if (!expectedProps.contains(node.propName)) {
@@ -281,18 +224,19 @@ import java.util.List;
expectedProps.add(node.propName); expectedProps.add(node.propName);
} }
} }
mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray()) TestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ " was not found."); + " was not found.");
} }
} }
private void verifyNode(final String propName, final PropertyNode actualNode) { private void verifyNode(final String propName, final PropertyNode actualNode) {
List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); final List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
final int size = (expectedNodeList != null ? expectedNodeList.size() : 0); final int size = (expectedNodeList != null ? expectedNodeList.size() : 0);
if (size > 0) { if (size > 0) {
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
PropertyNode expectedNode = expectedNodeList.get(i); final PropertyNode expectedNode = expectedNodeList.get(i);
List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>(); final List<PropertyNode> expectedButDifferentValueList =
new ArrayList<PropertyNode>();
if (expectedNode.propName.equals(propName)) { if (expectedNode.propName.equals(propName)) {
if (expectedNode.equals(actualNode)) { if (expectedNode.equals(actualNode)) {
expectedNodeList.remove(i); expectedNodeList.remove(i);
@@ -318,7 +262,7 @@ import java.util.List;
expectedButDifferentValueList); expectedButDifferentValueList);
} else { } else {
// There's no expected node with same propName. // There's no expected node with same propName.
mTestCase.fail("Unexpected property \"" + propName + "\" exists."); TestCase.fail("Unexpected property \"" + propName + "\" exists.");
} }
} }
} else { } else {
@@ -333,7 +277,7 @@ import java.util.List;
expectedButDifferentValueList); expectedButDifferentValueList);
} else { } else {
// There's no expected node with same propName. // There's no expected node with same propName.
mTestCase.fail("Unexpected property \"" + propName + "\" exists."); TestCase.fail("Unexpected property \"" + propName + "\" exists.");
} }
} }
} }
@@ -379,7 +323,7 @@ import java.util.List;
builder.append(expectedNode.toString()); builder.append(expectedNode.toString());
builder.append("\n"); builder.append("\n");
} }
mTestCase.fail("Property \"" + propName + "\" has wrong value.\n" TestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ builder.toString() + builder.toString()
+ " actual: " + actualNode.toString()); + " actual: " + actualNode.toString());
} }

View File

@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard.test_utils;
package android.pim.vcard;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardConfig;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
/** /**
@@ -24,8 +24,9 @@ import android.test.AndroidTestCase;
* Please do not add each unit test here. * Please do not add each unit test here.
*/ */
public class VCardTestsBase extends AndroidTestCase { public class VCardTestsBase extends AndroidTestCase {
public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8; public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8; public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC;
public static final int V40 = VCardConfig.VCARD_TYPE_V40_GENERIC;
// Do not modify these during tests. // Do not modify these during tests.
protected final ContentValues mContentValuesForQP; protected final ContentValues mContentValuesForQP;
@@ -41,6 +42,7 @@ public class VCardTestsBase extends AndroidTestCase {
public VCardTestsBase() { public VCardTestsBase() {
super(); super();
// Not using constants in vCard code since it may be wrong.
mContentValuesForQP = new ContentValues(); mContentValuesForQP = new ContentValues();
mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE"); mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForSJis = new ContentValues(); mContentValuesForSJis = new ContentValues();

View File

@@ -13,9 +13,8 @@
* License for the specific language governing permissions and limitations under * License for the specific language governing permissions and limitations under
* the License. * the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentProvider;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.EntityIterator; import android.content.EntityIterator;
@@ -26,50 +25,62 @@ import android.pim.vcard.VCardEntryConstructor;
import android.pim.vcard.VCardInterpreter; import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardInterpreterCollection; import android.pim.vcard.VCardInterpreterCollection;
import android.pim.vcard.VCardParser; import android.pim.vcard.VCardParser;
import android.pim.vcard.VCardParser_V21; import android.pim.vcard.VCardUtils;
import android.pim.vcard.VCardParser_V30;
import android.pim.vcard.exception.VCardException; import android.pim.vcard.exception.VCardException;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import android.test.mock.MockContext; import android.test.mock.MockContext;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import junit.framework.TestCase;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
/* package */ class CustomMockContext extends MockContext { /**
final ContentResolver mResolver; * <p>
public CustomMockContext(ContentResolver resolver) { * The class lets users checks that given expected vCard data are same as given actual vCard data.
mResolver = resolver; * Able to verify both vCard importer/exporter.
} * </p>
* <p>
@Override * First a user has to initialize the object by calling either
public ContentResolver getContentResolver() { * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
return mResolver; * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
} * </p>
} */
public class VCardVerifier {
/* package */ class VCardVerifier {
private static final String LOG_TAG = "VCardVerifier"; private static final String LOG_TAG = "VCardVerifier";
private static class CustomMockContext extends MockContext {
final ContentResolver mResolver;
public CustomMockContext(ContentResolver resolver) {
mResolver = resolver;
}
@Override
public ContentResolver getContentResolver() {
return mResolver;
}
}
private class VCardVerifierInternal implements VCardComposer.OneEntryHandler { private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
public boolean onInit(Context context) { public boolean onInit(Context context) {
return true; return true;
} }
public boolean onEntryCreated(String vcard) { public boolean onEntryCreated(String vcard) {
verifyOneVCard(vcard); verifyOneVCardForExport(vcard);
return true; return true;
} }
public void onTerminate() { public void onTerminate() {
} }
} }
private final AndroidTestCase mTestCase; private final AndroidTestCase mAndroidTestCase;
private final VCardVerifierInternal mVCardVerifierInternal; private final VCardVerifierInternal mVCardVerifierInternal;
private int mVCardType; private int mVCardType;
private boolean mIsV30;
private boolean mIsDoCoMo; private boolean mIsDoCoMo;
// Only one of them must be non-empty. // Only one of them must be non-empty.
@@ -83,9 +94,11 @@ import java.util.Arrays;
private ContentValuesVerifier mContentValuesVerifier; private ContentValuesVerifier mContentValuesVerifier;
private boolean mInitialized; private boolean mInitialized;
private boolean mVerified = false; private boolean mVerified = false;
private String mCharset;
// Called by VCardTestsBase
public VCardVerifier(AndroidTestCase androidTestCase) { public VCardVerifier(AndroidTestCase androidTestCase) {
mTestCase = androidTestCase; mAndroidTestCase = androidTestCase;
mVCardVerifierInternal = new VCardVerifierInternal(); mVCardVerifierInternal = new VCardVerifierInternal();
mExportTestResolver = null; mExportTestResolver = null;
mInputStream = null; mInputStream = null;
@@ -93,76 +106,101 @@ import java.util.Arrays;
mVerified = false; mVerified = false;
} }
public void initForExportTest(int vcardType) { // Should be called at the beginning of each import test.
if (mInitialized) {
mTestCase.fail("Already initialized");
}
mExportTestResolver = new ExportTestResolver(mTestCase);
mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mInitialized = true;
}
public void initForImportTest(int vcardType, int resId) { public void initForImportTest(int vcardType, int resId) {
if (mInitialized) { if (mInitialized) {
mTestCase.fail("Already initialized"); AndroidTestCase.fail("Already initialized");
} }
mVCardType = vcardType; mVCardType = vcardType;
mIsV30 = VCardConfig.isV30(vcardType);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
setInputResourceId(resId); setInputResourceId(resId);
mInitialized = true; mInitialized = true;
} }
// Should be called at the beginning of each export test.
public void initForExportTest(int vcardType) {
initForExportTest(vcardType, "UTF-8");
}
public void initForExportTest(int vcardType, String charset) {
if (mInitialized) {
AndroidTestCase.fail("Already initialized");
}
mExportTestResolver = new ExportTestResolver(mAndroidTestCase);
mVCardType = vcardType;
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mInitialized = true;
if (TextUtils.isEmpty(charset)) {
mCharset = "UTF-8";
} else {
mCharset = charset;
}
}
private void setInputResourceId(int resId) { private void setInputResourceId(int resId) {
InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId); InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId);
if (inputStream == null) { if (inputStream == null) {
mTestCase.fail("Wrong resId: " + resId); AndroidTestCase.fail("Wrong resId: " + resId);
} }
setInputStream(inputStream); setInputStream(inputStream);
} }
private void setInputStream(InputStream inputStream) { private void setInputStream(InputStream inputStream) {
if (mExportTestResolver != null) { if (mExportTestResolver != null) {
mTestCase.fail("addInputEntry() is called."); AndroidTestCase.fail("addInputEntry() is called.");
} else if (mInputStream != null) { } else if (mInputStream != null) {
mTestCase.fail("InputStream is already set"); AndroidTestCase.fail("InputStream is already set");
} }
mInputStream = inputStream; mInputStream = inputStream;
} }
public ContactEntry addInputEntry() { public ContactEntry addInputEntry() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized"); AndroidTestCase.fail("Not initialized");
} }
if (mInputStream != null) { if (mInputStream != null) {
mTestCase.fail("setInputStream is called"); AndroidTestCase.fail("setInputStream is called");
} }
return mExportTestResolver.addInputContactEntry(); return mExportTestResolver.addInputContactEntry();
} }
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized"); AndroidTestCase.fail("Not initialized");
} }
if (mPropertyNodesVerifier == null) { if (mPropertyNodesVerifier == null) {
mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase); mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase);
} }
PropertyNodesVerifierElem elem = return mPropertyNodesVerifier.addPropertyNodesVerifierElem();
mPropertyNodesVerifier.addPropertyNodesVerifierElem(); }
elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElemWithoutVersion();
final String versionString;
if (VCardConfig.isVersion21(mVCardType)) {
versionString = "2.1";
} else if (VCardConfig.isVersion30(mVCardType)) {
versionString = "3.0";
} else if (VCardConfig.isVersion40(mVCardType)) {
versionString = "4.0";
} else {
throw new RuntimeException("Unexpected vcard type during a unit test");
}
elem.addExpectedNodeWithOrder("VERSION", versionString);
return elem; return elem;
} }
public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized"); AndroidTestCase.fail("Not initialized");
} }
PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
if (mIsV30) { if (VCardConfig.isVersion40(mVCardType)) {
elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", ""); elem.addExpectedNodeWithOrder("FN", "");
} else if (VCardConfig.isVersion30(mVCardType)) {
elem.addExpectedNodeWithOrder("N", "");
elem.addExpectedNodeWithOrder("FN", "");
} else if (mIsDoCoMo) { } else if (mIsDoCoMo) {
elem.addExpectedNodeWithOrder("N", ""); elem.addExpectedNodeWithOrder("N", "");
} }
@@ -171,64 +209,75 @@ import java.util.Arrays;
public LineVerifierElem addLineVerifierElem() { public LineVerifierElem addLineVerifierElem() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized"); AndroidTestCase.fail("Not initialized");
} }
if (mLineVerifier == null) { if (mLineVerifier == null) {
mLineVerifier = new LineVerifier(mTestCase, mVCardType); mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType);
} }
return mLineVerifier.addLineVerifierElem(); return mLineVerifier.addLineVerifierElem();
} }
public ContentValuesVerifierElem addContentValuesVerifierElem() { public ContentValuesVerifierElem addContentValuesVerifierElem() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized"); AndroidTestCase.fail("Not initialized");
} }
if (mContentValuesVerifier == null) { if (mContentValuesVerifier == null) {
mContentValuesVerifier = new ContentValuesVerifier(); mContentValuesVerifier = new ContentValuesVerifier();
} }
return mContentValuesVerifier.addElem(mTestCase); return mContentValuesVerifier.addElem(mAndroidTestCase);
} }
private void verifyOneVCard(final String vcard) { /**
// Log.d("@@@", vcard); * Sets up sub-verifiers correctly and try parse given vCard as InputStream.
final VCardInterpreter builder; * Errors around InputStream must be handled outside this method.
*
* Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}.
*/
private void verifyWithInputStream(InputStream is) throws IOException {
final VCardInterpreter interpreter;
if (mContentValuesVerifier != null) { if (mContentValuesVerifier != null) {
final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier; final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType);
final VCardEntryConstructor vcardDataBuilder = constructor.addEntryHandler(mContentValuesVerifier);
new VCardEntryConstructor(mVCardType);
vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
if (mPropertyNodesVerifier != null) { if (mPropertyNodesVerifier != null) {
builder = new VCardInterpreterCollection(Arrays.asList( interpreter = new VCardInterpreterCollection(Arrays.asList(
mPropertyNodesVerifier, vcardDataBuilder)); mPropertyNodesVerifier, constructor));
} else { } else {
builder = vnodeBuilder; interpreter = constructor;
} }
} else { } else {
if (mPropertyNodesVerifier != null) { if (mPropertyNodesVerifier != null) {
builder = mPropertyNodesVerifier; interpreter = mPropertyNodesVerifier;
} else { } else {
return; interpreter = null;
} }
} }
final VCardParser parser = try {
(mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21()); // Note: we must not specify charset toward vCard parsers. This code checks whether
// those parsers are able to encode given binary without any extra information for
// charset.
final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType);
parser.parse(is, interpreter);
} catch (VCardException e) {
AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage());
}
}
private void verifyOneVCardForExport(final String vcard) {
Log.d(LOG_TAG, vcard);
InputStream is = null; InputStream is = null;
try { try {
String charset = is = new ByteArrayInputStream(vcard.getBytes(mCharset));
(VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8"); verifyWithInputStream(is);
is = new ByteArrayInputStream(vcard.getBytes(charset));
mTestCase.assertEquals(true, parser.parse(is, null, builder));
} catch (IOException e) { } catch (IOException e) {
mTestCase.fail("Unexpected IOException: " + e.getMessage()); AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
} catch (VCardException e) {
mTestCase.fail("Unexpected VCardException: " + e.getMessage());
} finally { } finally {
if (is != null) { if (is != null) {
try { try {
is.close(); is.close();
} catch (IOException e) { } catch (IOException e) {
AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
} }
} }
} }
@@ -236,33 +285,41 @@ import java.util.Arrays;
public void verify() { public void verify() {
if (!mInitialized) { if (!mInitialized) {
mTestCase.fail("Not initialized."); TestCase.fail("Not initialized.");
} }
if (mVerified) { if (mVerified) {
mTestCase.fail("verify() was called twice."); TestCase.fail("verify() was called twice.");
} }
if (mInputStream != null) { if (mInputStream != null) {
try { if (mExportTestResolver != null){
verifyForImportTest(); TestCase.fail("There are two input sources.");
} catch (IOException e) {
mTestCase.fail("IOException was thrown: " + e.getMessage());
} catch (VCardException e) {
mTestCase.fail("VCardException was thrown: " + e.getMessage());
} }
verifyForImportTest();
} else if (mExportTestResolver != null){ } else if (mExportTestResolver != null){
verifyForExportTest(); verifyForExportTest();
} else { } else {
mTestCase.fail("No input is determined"); TestCase.fail("No input is determined");
} }
mVerified = true; mVerified = true;
} }
private void verifyForImportTest() throws IOException, VCardException { private void verifyForImportTest() {
if (mLineVerifier != null) { if (mLineVerifier != null) {
mTestCase.fail("Not supported now."); AndroidTestCase.fail("Not supported now.");
} }
if (mContentValuesVerifier != null) {
mContentValuesVerifier.verify(mInputStream, mVCardType); try {
verifyWithInputStream(mInputStream);
} catch (IOException e) {
AndroidTestCase.fail("IOException was thrown: " + e.getMessage());
} finally {
if (mInputStream != null) {
try {
mInputStream.close();
} catch (IOException e) {
}
}
} }
} }
@@ -287,7 +344,7 @@ import java.util.Arrays;
private void verifyForExportTest() { private void verifyForExportTest() {
final VCardComposer composer = final VCardComposer composer =
new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType); new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
composer.addHandler(mLineVerifier); composer.addHandler(mLineVerifier);
composer.addHandler(mVCardVerifierInternal); composer.addHandler(mVCardVerifierInternal);
if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
@@ -303,7 +360,7 @@ import java.util.Arrays;
composer.createOneEntry(mockGetEntityIteratorMethod)); composer.createOneEntry(mockGetEntityIteratorMethod));
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
AndroidTestCase.fail(); AndroidTestCase.fail(e.toString());
} }
} }
} finally { } finally {

View File

@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -13,18 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package android.pim.vcard; package android.pim.vcard.test_utils;
import android.content.ContentValues; import android.content.ContentValues;
import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardConfig; import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardInterpreter;
import android.pim.vcard.VCardUtils;
import android.util.Base64;
import android.util.CharsetUtils; import android.util.CharsetUtils;
import android.util.Log; import android.util.Log;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.net.QuotedPrintableCodec;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@@ -32,34 +30,29 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Store the parse result to custom datastruct: VNode, PropertyNode * <p>
* The class storing the parse result to custom datastruct:
* {@link VNode}, and {@link PropertyNode}.
* Maybe several vcard instance, so use vNodeList to store. * Maybe several vcard instance, so use vNodeList to store.
* VNode: standy by a vcard instance. * </p>
* PropertyNode: standy by a property line of a card. * <p>
* * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
* Previously used in main vCard handling code but now exists only for testing. * </p>
*/ */
public class VNodeBuilder implements VCardInterpreter { public class VNodeBuilder implements VCardInterpreter {
static private String LOG_TAG = "VNodeBuilder"; static private String LOG_TAG = "VNodeBuilder";
/**
* If there's no other information available, this class uses this charset for encoding
* byte arrays.
*/
static public String TARGET_CHARSET = "UTF-8";
/** type=VNode */
public List<VNode> vNodeList = new ArrayList<VNode>(); public List<VNode> vNodeList = new ArrayList<VNode>();
private int mNodeListPos = 0; private int mNodeListPos = 0;
private VNode mCurrentVNode; private VNode mCurrentVNode;
private PropertyNode mCurrentPropNode; private PropertyNode mCurrentPropNode;
private String mCurrentParamType; private String mCurrentParamType;
/** /**
* The charset using which VParser parses the text. * The charset using which VParser parses the text.
*/ */
private String mSourceCharset; private String mSourceCharset;
/** /**
* The charset with which byte array is encoded to String. * The charset with which byte array is encoded to String.
*/ */
@@ -68,27 +61,15 @@ public class VNodeBuilder implements VCardInterpreter {
private boolean mStrictLineBreakParsing; private boolean mStrictLineBreakParsing;
public VNodeBuilder() { public VNodeBuilder() {
this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false); this(VCardConfig.DEFAULT_IMPORT_CHARSET, false);
} }
public VNodeBuilder(String charset, boolean strictLineBreakParsing) { public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
this(null, charset, strictLineBreakParsing); mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
/**
* @hide sourceCharset is temporal.
*/
public VNodeBuilder(String sourceCharset, String targetCharset,
boolean strictLineBreakParsing) {
if (sourceCharset != null) {
mSourceCharset = sourceCharset;
} else {
mSourceCharset = VCardConfig.DEFAULT_CHARSET;
}
if (targetCharset != null) { if (targetCharset != null) {
mTargetCharset = targetCharset; mTargetCharset = targetCharset;
} else { } else {
mTargetCharset = TARGET_CHARSET; mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
} }
mStrictLineBreakParsing = strictLineBreakParsing; mStrictLineBreakParsing = strictLineBreakParsing;
} }
@@ -149,7 +130,6 @@ public class VNodeBuilder implements VCardInterpreter {
mCurrentPropNode.propName = name; mCurrentPropNode.propName = name;
} }
// Used only in VCard.
public void propertyGroup(String group) { public void propertyGroup(String group) {
mCurrentPropNode.propGroupSet.add(group); mCurrentPropNode.propGroupSet.add(group);
} }
@@ -159,6 +139,12 @@ public class VNodeBuilder implements VCardInterpreter {
} }
public void propertyParamValue(String value) { public void propertyParamValue(String value) {
if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
value = VCardUtils.convertStringCharset(value,
VCardConfig.DEFAULT_INTERMEDIATE_CHARSET,
VCardConfig.DEFAULT_IMPORT_CHARSET);
}
if (mCurrentParamType == null || if (mCurrentParamType == null ||
mCurrentParamType.equalsIgnoreCase("TYPE")) { mCurrentParamType.equalsIgnoreCase("TYPE")) {
mCurrentPropNode.paramMap_TYPE.add(value); mCurrentPropNode.paramMap_TYPE.add(value);
@@ -192,71 +178,11 @@ public class VNodeBuilder implements VCardInterpreter {
encoding = encoding.toUpperCase(); encoding = encoding.toUpperCase();
if (encoding.equals("BASE64") || encoding.equals("B")) { if (encoding.equals("BASE64") || encoding.equals("B")) {
// Assume BASE64 is used only when the number of values is 1. // Assume BASE64 is used only when the number of values is 1.
mCurrentPropNode.propValue_bytes = mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP);
Base64.decodeBase64(value.getBytes());
return value; return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) { } else if (encoding.equals("QUOTED-PRINTABLE")) {
String quotedPrintable = value return VCardUtils.parseQuotedPrintable(
.replaceAll("= ", " ").replaceAll("=\t", "\t"); value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
String[] lines;
if (mStrictLineBreakParsing) {
lines = quotedPrintable.split("\r\n");
} else {
StringBuilder builder = new StringBuilder();
int length = quotedPrintable.length();
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < length; i++) {
char ch = quotedPrintable.charAt(i);
if (ch == '\n') {
list.add(builder.toString());
builder = new StringBuilder();
} else if (ch == '\r') {
list.add(builder.toString());
builder = new StringBuilder();
if (i < length - 1) {
char nextCh = quotedPrintable.charAt(i + 1);
if (nextCh == '\n') {
i++;
}
}
} else {
builder.append(ch);
}
}
String finalLine = builder.toString();
if (finalLine.length() > 0) {
list.add(finalLine);
}
lines = list.toArray(new String[0]);
}
StringBuilder builder = new StringBuilder();
for (String line : lines) {
if (line.endsWith("=")) {
line = line.substring(0, line.length() - 1);
}
builder.append(line);
}
byte[] bytes;
try {
bytes = builder.toString().getBytes(mSourceCharset);
} catch (UnsupportedEncodingException e1) {
Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset);
bytes = builder.toString().getBytes();
}
try {
bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes);
} catch (DecoderException e) {
Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e);
return "";
}
try {
return new String(bytes, targetCharset);
} catch (UnsupportedEncodingException e) {
Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset);
return new String(bytes);
}
} }
// Unknown encoding. Fall back to default. // Unknown encoding. Fall back to default.
} }
@@ -309,6 +235,6 @@ public class VNodeBuilder implements VCardInterpreter {
} }
public String getResult(){ public String getResult(){
return null; throw new RuntimeException("Not supported");
} }
} }