Merge "Calculate audio media drift time from AudioSource" into gingerbread
diff --git a/api/9.xml b/api/9.xml
index 46f087f..b266352 100644
--- a/api/9.xml
+++ b/api/9.xml
@@ -793,17 +793,6 @@
visibility="public"
>
</field>
-<field name="READ_OWNER_DATA"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.permission.READ_OWNER_DATA""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="READ_PHONE_STATE"
type="java.lang.String"
transient="false"
@@ -1233,17 +1222,6 @@
visibility="public"
>
</field>
-<field name="WRITE_OWNER_DATA"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.permission.WRITE_OWNER_DATA""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="WRITE_SECURE_SETTINGS"
type="java.lang.String"
transient="false"
diff --git a/api/current.xml b/api/current.xml
index 3c08549..f7a5954 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -712,7 +712,7 @@
value=""android.permission.PERSISTENT_ACTIVITY""
static="true"
final="true"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</field>
@@ -793,17 +793,6 @@
visibility="public"
>
</field>
-<field name="READ_OWNER_DATA"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.permission.READ_OWNER_DATA""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="READ_PHONE_STATE"
type="java.lang.String"
transient="false"
@@ -1233,17 +1222,6 @@
visibility="public"
>
</field>
-<field name="WRITE_OWNER_DATA"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value=""android.permission.WRITE_OWNER_DATA""
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
<field name="WRITE_SECURE_SETTINGS"
type="java.lang.String"
transient="false"
@@ -20516,7 +20494,7 @@
synchronized="false"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="isPersistent" type="boolean">
@@ -203586,7 +203564,7 @@
synchronized="true"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
</method>
@@ -204134,7 +204112,7 @@
synchronized="true"
static="false"
final="false"
- deprecated="not deprecated"
+ deprecated="deprecated"
visibility="public"
>
<parameter name="pluginsPath" type="java.lang.String">
@@ -252670,7 +252648,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="lng" type="long">
+<parameter name="l" type="long">
</parameter>
</method>
<method name="append"
@@ -252748,7 +252726,7 @@
deprecated="not deprecated"
visibility="public"
>
-<parameter name="ch" type="char[]">
+<parameter name="chars" type="char[]">
</parameter>
</method>
<method name="append"
@@ -307319,9 +307297,9 @@
>
<parameter name="number" type="java.lang.Object">
</parameter>
-<parameter name="toAppendTo" type="java.lang.StringBuffer">
+<parameter name="buffer" type="java.lang.StringBuffer">
</parameter>
-<parameter name="pos" type="java.text.FieldPosition">
+<parameter name="position" type="java.text.FieldPosition">
</parameter>
</method>
<method name="getDecimalFormatSymbols"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f7a9a18..6e6e86f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1620,6 +1620,9 @@
}
/**
+ * @deprecated This functionality will be removed in the future; please do
+ * not use.
+ *
* Control whether this activity is required to be persistent. By default
* activities are not persistent; setting this to true will prevent the
* system from stopping this activity or its process when running low on
@@ -1634,6 +1637,7 @@
* persistent, true if so, false for the normal
* behavior.
*/
+ @Deprecated
public void setPersistent(boolean isPersistent) {
if (mParent == null) {
try {
diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java
index 875c29e..dcfe980 100644
--- a/core/java/android/pim/vcard/JapaneseUtils.java
+++ b/core/java/android/pim/vcard/JapaneseUtils.java
@@ -27,7 +27,6 @@
new HashMap<Character, String>();
static {
- // There's no logical mapping rule in Unicode. Sigh.
sHalfWidthMap.put('\u3001', "\uFF64");
sHalfWidthMap.put('\u3002', "\uFF61");
sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@
}
/**
- * 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
* @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)) {
return sHalfWidthMap.get(ch);
} else {
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java
index d634672..b2007f2 100644
--- a/core/java/android/pim/vcard/VCardBuilder.java
+++ b/core/java/android/pim/vcard/VCardBuilder.java
@@ -30,11 +30,10 @@
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
+import android.util.Base64;
import android.util.CharsetUtils;
import android.util.Log;
-import org.apache.commons.codec.binary.Base64;
-
import java.io.UnsupportedEncodingException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
@@ -47,7 +46,23 @@
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 {
private static final String LOG_TAG = "VCardBuilder";
@@ -75,81 +90,129 @@
private static final String VCARD_WS = " ";
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_BASE64_V21 = "ENCODING=BASE64";
- private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b";
+ private static final String VCARD_PARAM_ENCODING_QP =
+ "ENCODING=" + VCardConstants.PARAM_ENCODING_QP;
+ private static final String VCARD_PARAM_ENCODING_BASE64_V21 =
+ "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 UTF_8 = "UTF-8";
private final int mVCardType;
- private final boolean mIsV30;
+ private final boolean mIsV30OrV40;
private final boolean mIsJapaneseMobilePhone;
private final boolean mOnlyOneNoteFieldIsAvailable;
private final boolean mIsDoCoMo;
private final boolean mShouldUseQuotedPrintable;
private final boolean mUsesAndroidProperty;
private final boolean mUsesDefactProperty;
- private final boolean mUsesUtf8;
- private final boolean mUsesShiftJis;
private final boolean mAppendTypeParamName;
private final boolean mRefrainsQPToNameProperties;
private final boolean mNeedsToConvertPhoneticString;
private final boolean mShouldAppendCharsetParam;
- private final String mCharsetString;
+ private final String mCharset;
private final String mVCardCharsetParameter;
private StringBuilder mBuilder;
private boolean mEndAppended;
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;
- 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);
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType);
mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType);
mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType);
mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType);
- mUsesUtf8 = VCardConfig.usesUtf8(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType);
mAppendTypeParamName = VCardConfig.appendTypeParamName(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) {
- String charset;
- 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;
+ if (VCardConfig.isDoCoMo(vcardType)) {
+ if (!SHIFT_JIS.equalsIgnoreCase(charset)) {
+ 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;
+ }
+ } 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;
} else {
- mCharsetString = UTF_8;
- mVCardCharsetParameter = "CHARSET=" + UTF_8;
+ if (TextUtils.isEmpty(charset)) {
+ 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();
}
@@ -158,9 +221,14 @@
mBuilder = new StringBuilder();
mEndAppended = false;
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);
} else {
+ if (!VCardConfig.isVersion21(mVCardType)) {
+ Log.w(LOG_TAG, "Unknown vCard version detected.");
+ }
appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21);
}
}
@@ -227,18 +295,127 @@
}
/**
+ * 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
* may get confused with multiple "N", "FN", etc. properties, though it is valid in
* vCard spec.
*/
public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) {
+ if (VCardConfig.isVersion40(mVCardType)) {
+ return appendNamePropertiesV40(contentValuesList);
+ }
+
if (contentValuesList == null || contentValuesList.isEmpty()) {
- if (mIsDoCoMo) {
- appendLine(VCardConstants.PROPERTY_N, "");
- } else if (mIsV30) {
+ if (VCardConfig.isVersion30(mVCardType)) {
// 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_FN, "");
+ } else if (mIsDoCoMo) {
+ appendLine(VCardConstants.PROPERTY_N, "");
}
return this;
}
@@ -360,6 +537,7 @@
encodeQuotedPrintable(displayName) :
escapeCharacters(displayName);
+ // N
mBuilder.append(VCardConstants.PROPERTY_N);
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
@@ -376,11 +554,13 @@
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_ITEM_SEPARATOR);
mBuilder.append(VCARD_END_OF_LINE);
+
+ // FN
mBuilder.append(VCardConstants.PROPERTY_FN);
// 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
- // importer allows this vioration.
+ // when it would be useful or necessary for external importers,
+ // assuming the external importer allows this vioration of the spec.
if (shouldAppendCharsetParam(displayName)) {
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(mVCardCharsetParameter);
@@ -388,8 +568,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedDisplayName);
mBuilder.append(VCARD_END_OF_LINE);
- } else if (mIsV30) {
- // vCard 3.0 specification requires these fields.
+ } else if (VCardConfig.isVersion30(mVCardType)) {
appendLine(VCardConstants.PROPERTY_N, "");
appendLine(VCardConstants.PROPERTY_FN, "");
} else if (mIsDoCoMo) {
@@ -400,6 +579,9 @@
return this;
}
+ /**
+ * Emits SOUND;IRMC, SORT-STRING, and de-fact values for phonetic names like X-PHONETIC-FAMILY.
+ */
private void appendPhoneticNameFields(final ContentValues contentValues) {
final String phoneticFamilyName;
final String phoneticMiddleName;
@@ -439,13 +621,18 @@
return;
}
- // Try to emit the field(s) related to phonetic name.
- if (mIsV30) {
- final String sortString = VCardUtils
- .constructNameFromElements(mVCardType,
+ if (VCardConfig.isVersion40(mVCardType)) {
+ // We don't want SORT-STRING anyway.
+ } else if (VCardConfig.isVersion30(mVCardType)) {
+ final String sortString =
+ VCardUtils.constructNameFromElements(mVCardType,
phoneticFamilyName, phoneticMiddleName, phoneticGivenName);
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(mVCardCharsetParameter);
}
@@ -454,18 +641,18 @@
mBuilder.append(VCARD_END_OF_LINE);
} else if (mIsJapaneseMobilePhone) {
// 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).
- // We chose to use DoCoMo's way when the device is Japanese one
- // since it is supported by
- // a lot of Japanese mobile phones. This is "X-" property, so
- // any parser hopefully would not get confused with this.
+ // We use DoCoMo's way when the device is Japanese one since it is already
+ // supported by a lot of Japanese mobile phones.
+ // This is "X-" property, so any parser hopefully would not get
+ // confused with this.
//
// Also, DoCoMo's specification requires vCard composer to use just the first
// column.
// i.e.
- // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
- // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
+ // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;;
+ // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;;
mBuilder.append(VCardConstants.PROPERTY_SOUND);
mBuilder.append(VCARD_PARAM_SEPARATOR);
mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N);
@@ -519,13 +706,14 @@
mBuilder.append(encodedPhoneticGivenName);
}
}
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
- mBuilder.append(VCARD_ITEM_SEPARATOR);
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix
+ mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix
mBuilder.append(VCARD_END_OF_LINE);
}
+ Log.d("@@@", "hoge");
if (mUsesDefactProperty) {
if (!TextUtils.isEmpty(phoneticGivenName)) {
final boolean reallyUseQuotedPrintable =
@@ -549,7 +737,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticGivenName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticMiddleName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -572,7 +760,7 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticMiddleName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticGivenName))
if (!TextUtils.isEmpty(phoneticFamilyName)) {
final boolean reallyUseQuotedPrintable =
(mShouldUseQuotedPrintable &&
@@ -595,13 +783,13 @@
mBuilder.append(VCARD_DATA_SEPARATOR);
mBuilder.append(encodedPhoneticFamilyName);
mBuilder.append(VCARD_END_OF_LINE);
- }
+ } // if (!TextUtils.isEmpty(phoneticFamilyName))
}
}
public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) {
final boolean useAndroidProperty;
- if (mIsV30) {
+ if (mIsV30OrV40) { // These specifications have NICKNAME property.
useAndroidProperty = false;
} else if (mUsesAndroidProperty) {
useAndroidProperty = true;
@@ -922,21 +1110,21 @@
encodedCountry = escapeCharacters(rawCountry);
encodedNeighborhood = escapeCharacters(rawNeighborhood);
}
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(encodedPoBox);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedStreet);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedLocality);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedRegion);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedPostalCode);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedCountry);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(encodedPoBox);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(encodedStreet);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(encodedLocality);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(encodedRegion);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(encodedPostalCode);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
+ addressBuilder.append(encodedCountry);
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
} else { // VCardUtils.areAllEmpty(rawAddressArray) == true
// Try to use FORMATTED_ADDRESS instead.
final String rawFormattedAddress =
@@ -959,16 +1147,16 @@
// 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
// be needed.
- final StringBuffer addressBuffer = new StringBuffer();
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(encodedFormattedAddress);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
- addressBuffer.append(VCARD_ITEM_SEPARATOR);
+ final StringBuilder addressBuilder = new StringBuilder();
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address
+ addressBuilder.append(encodedFormattedAddress);
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code
+ addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country
return new PostalStruct(
- reallyUseQuotedPrintable, appendCharset, addressBuffer.toString());
+ reallyUseQuotedPrintable, appendCharset, addressBuilder.toString());
}
}
@@ -1108,7 +1296,8 @@
Log.d(LOG_TAG, "Unknown photo type. Ignored.");
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)) {
appendPhotoLine(photoString, photoType);
}
@@ -1165,6 +1354,8 @@
}
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) {
String primaryBirthday = null;
String secondaryBirthday = null;
@@ -1232,16 +1423,19 @@
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,
final ContentValues contentValues,
- final boolean isPrimary, final boolean emitLineEveryTime) {
+ final boolean isPrimary, final boolean emitEveryTime) {
final boolean reallyUseQuotedPrintable;
final boolean appendCharset;
final String addressValue;
{
PostalStruct postalStruct = tryConstructPostalStruct(contentValues);
if (postalStruct == null) {
- if (emitLineEveryTime) {
+ if (emitEveryTime) {
reallyUseQuotedPrintable = false;
appendCharset = false;
addressValue = "";
@@ -1463,7 +1657,7 @@
parameterList.add(VCardConstants.PARAM_TYPE_VOICE);
} else if (VCardUtils.isMobilePhoneLabel(label)) {
parameterList.add(VCardConstants.PARAM_TYPE_CELL);
- } else if (mIsV30) {
+ } else if (mIsV30OrV40) {
// This label is appropriately encoded in appendTypeParameters.
parameterList.add(label);
} else {
@@ -1526,8 +1720,8 @@
StringBuilder tmpBuilder = new StringBuilder();
tmpBuilder.append(VCardConstants.PROPERTY_PHOTO);
tmpBuilder.append(VCARD_PARAM_SEPARATOR);
- if (mIsV30) {
- tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30);
+ if (mIsV30OrV40) {
+ tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_AS_B);
} else {
tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21);
}
@@ -1559,7 +1753,8 @@
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)) {
return;
}
@@ -1681,7 +1876,7 @@
encodedValue = encodeQuotedPrintable(rawValue);
} else {
// TODO: one line may be too huge, which may be invalid in vCard spec, though
- // several (even well-known) applications do not care this.
+ // several (even well-known) applications do not care that violation.
encodedValue = escapeCharacters(rawValue);
}
@@ -1744,7 +1939,12 @@
// which would be recommended way in vcard 3.0 though not valid in vCard 2.1.
boolean first = true;
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
// 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.
@@ -1756,7 +1956,7 @@
} else {
mBuilder.append(VCARD_PARAM_SEPARATOR);
}
- appendTypeParameter(VCardUtils.toStringAvailableAsV30ParameValue(typeValue));
+ appendTypeParameter(encoded);
} else { // vCard 2.1
if (!VCardUtils.isV21Word(typeValue)) {
continue;
@@ -1783,7 +1983,8 @@
// device is DoCoMo's (just for safety).
//
// 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(type);
@@ -1825,9 +2026,9 @@
byte[] strArray = null;
try {
- strArray = str.getBytes(mCharsetString);
+ strArray = str.getBytes(mCharset);
} catch (UnsupportedEncodingException e) {
- Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. "
+ Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. "
+ "Try default charset");
strArray = str.getBytes();
}
@@ -1893,7 +2094,7 @@
break;
}
case '\\': {
- if (mIsV30) {
+ if (mIsV30OrV40) {
tmpBuilder.append("\\\\");
break;
} else {
@@ -1911,7 +2112,7 @@
break;
}
case ',': {
- if (mIsV30) {
+ if (mIsV30OrV40) {
tmpBuilder.append("\\,");
} else {
tmpBuilder.append(ch);
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 0e8b665..193cf1e 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -19,16 +19,12 @@
import android.content.ContentValues;
import android.content.Context;
import android.content.Entity;
-import android.content.EntityIterator;
import android.content.Entity.NamedContentValues;
+import android.content.EntityIterator;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
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.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -41,6 +37,11 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.ContactsContract.RawContactsEntity;
+import android.text.TextUtils;
import android.util.CharsetUtils;
import android.util.Log;
@@ -61,15 +62,11 @@
/**
* <p>
- * The class for composing VCard from Contacts information. Note that this is
- * completely differnt implementation from
- * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore.
+ * The class for composing vCard from Contacts information.
* </p>
- *
* <p>
* Usually, this class should be used like this.
* </p>
- *
* <pre class="prettyprint">VCardComposer composer = null;
* try {
* composer = new VCardComposer(context);
@@ -93,15 +90,18 @@
* if (composer != null) {
* 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 {
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 =
"Failed to get database information";
@@ -119,6 +119,8 @@
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 UTF_8 = "UTF-8";
@@ -141,7 +143,7 @@
sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ);
sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER);
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 {
@@ -152,37 +154,37 @@
/**
* <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>
* The input OutputStream object is closed() on {@link #onTerminate()}.
- * Must not close the stream outside.
+ * Must not close the stream outside this class.
* </p>
*/
- public class HandlerForOutputStream implements OneEntryHandler {
+ public final class HandlerForOutputStream implements OneEntryHandler {
@SuppressWarnings("hiding")
- private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream";
-
- final private OutputStream mOutputStream; // mWriter will close this.
- private Writer mWriter;
+ private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream";
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.
*/
- public HandlerForOutputStream(OutputStream outputStream) {
+ public HandlerForOutputStream(final OutputStream outputStream) {
mOutputStream = outputStream;
}
- public boolean onInit(Context context) {
+ public boolean onInit(final Context context) {
try {
mWriter = new BufferedWriter(new OutputStreamWriter(
- mOutputStream, mCharsetString));
+ mOutputStream, mCharset));
} 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!): "
- + mCharsetString;
+ + mCharset;
return false;
}
@@ -235,14 +237,19 @@
"IOException during closing the output stream: "
+ e.getMessage());
} finally {
- try {
- mWriter.close();
- } catch (IOException e) {
- }
+ closeOutputStream();
}
}
}
+ public void closeOutputStream() {
+ try {
+ mWriter.close();
+ } catch (IOException e) {
+ Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring.");
+ }
+ }
+
@Override
public void finalize() {
if (!mOnTerminateIsCalled) {
@@ -257,11 +264,10 @@
private final ContentResolver mContentResolver;
private final boolean mIsDoCoMo;
- private final boolean mUsesShiftJis;
private Cursor mCursor;
private int mIdColumn;
- private final String mCharsetString;
+ private final String mCharset;
private boolean mTerminateIsCalled;
private final List<OneEntryHandler> mHandlerList;
@@ -272,21 +278,39 @@
};
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) {
- this(context, vcardType, true);
+ this(context, vcardType, null, true);
}
- public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) {
- this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
+ public VCardComposer(Context context, int vcardType, String charset) {
+ 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.
+ *
+ * @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) {
mContext = context;
mVCardType = vcardType;
@@ -294,30 +318,67 @@
mContentResolver = context.getContentResolver();
mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
mHandlerList = new ArrayList<OneEntryHandler>();
- if (mIsDoCoMo) {
- String charset;
- 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;
+ charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset);
+ final boolean shouldAppendCharsetParam = !(
+ VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset));
+
+ if (mIsDoCoMo || shouldAppendCharsetParam) {
+ if (SHIFT_JIS.equalsIgnoreCase(charset)) {
+ 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 {
- 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 @@
}
if (mCareHandlerErrors) {
- List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
+ final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
for (OneEntryHandler handler : mHandlerList) {
if (!handler.onInit(mContext)) {
@@ -414,7 +475,7 @@
mErrorReason = FAILURE_REASON_NOT_INITIALIZED;
return false;
}
- String vcard;
+ final String vcard;
try {
if (mIdColumn >= 0) {
vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
@@ -437,8 +498,7 @@
mCursor.moveToNext();
}
- // This function does not care the OutOfMemoryError on the handler side
- // :-P
+ // This function does not care the OutOfMemoryError on the handler side :-P
if (mCareHandlerErrors) {
List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>(
mHandlerList.size());
@@ -457,7 +517,7 @@
}
private String createOneEntryInternal(final String contactId,
- Method getEntityIteratorMethod) throws VCardException {
+ final Method getEntityIteratorMethod) throws VCardException {
final Map<String, List<ContentValues>> contentValuesListMap =
new HashMap<String, List<ContentValues>>();
// The resolver may return the entity iterator with no data. It is possible.
@@ -466,12 +526,13 @@
EntityIterator entityIterator = null;
try {
final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
+ // .appendQueryParameter("for_export_only", "1")
.appendQueryParameter(Data.FOR_EXPORT_ONLY, "1")
.build();
final String selection = Data.CONTACT_ID + "=?";
final String[] selectionArgs = new String[] {contactId};
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 {
entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
mContentResolver, uri, selection, selectionArgs, null);
@@ -527,22 +588,35 @@
}
}
- final VCardBuilder builder = new VCardBuilder(mVCardType);
- 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));
- if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
- builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+ return buildVCard(contentValuesListMap);
+ }
+
+ /**
+ * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in
+ * {ContactsContract}. Developers can override this method to customize the output.
+ */
+ public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) {
+ if (contentValuesListMap == null) {
+ 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));
+ if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
+ builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
+ }
+ 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();
}
- 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() {
@@ -565,26 +639,38 @@
@Override
public void finalize() {
if (!mTerminateIsCalled) {
+ Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step.");
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() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
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() {
if (mCursor == null) {
+ Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
}
/**
- * @return Return the error reason if possible.
+ * @return Returns the error reason.
*/
public String getErrorReason() {
return mErrorReason;
diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java
index 8219840..8e759e3 100644
--- a/core/java/android/pim/vcard/VCardConfig.java
+++ b/core/java/android/pim/vcard/VCardConfig.java
@@ -38,20 +38,43 @@
/* 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;
- /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones.
- /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones.
- /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4;
+ /**
+ * <p>
+ * The charset used during import.
+ * </p>
+ * <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.
- public static final String DEFAULT_CHARSET = "iso-8859-1";
-
- public static final int FLAG_V21 = 0;
- public static final int FLAG_V30 = 1;
+ /**
+ * The charset used when there's no information affbout what charset should be used to
+ * encode the binary given from vCard.
+ */
+ public static final String DEFAULT_IMPORT_CHARSET = "UTF-8";
+ 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_EUROPE = 0x4;
@@ -59,144 +82,140 @@
private static final int NAME_ORDER_MASK = 0xC;
// 0x10 is reserved for safety
-
- private static final int FLAG_CHARSET_UTF8 = 0;
- private static final int FLAG_CHARSET_SHIFT_JIS = 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
* 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.
* When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME")
* instead of just dropping it.
- *
+ * </p>
+ * <p>
* vCard parser code automatically parses the field emitted even when this flag is off.
- *
- * Note that this flag does not assure all the information must be hold in the emitted vCard.
+ * </p>
*/
private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000;
/**
+ * <p>
* The flag indicating the vCard composer will add some "X-" properties seen in the
* vCard data emitted by the other softwares/devices when the formal vCard specification
- * does not have appropriate field(s) for that data.
- *
+ * 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
* for phonetic name (how the name is pronounced), seen in the vCard emitted by some other
* non-Android devices/softwares. We chose to enable the vCard composer to use those
* defact properties since they are also useful for Android devices.
- *
+ * </p>
+ * <p>
* Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0
* allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens
* in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties.
+ * </p>
*/
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
* 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.
+ * </p>
*/
private static final int FLAG_DOCOMO = 0x20000000;
/**
- * <P>
+ * <p>
* 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).
- * </P>
- * <P>
+ * </p>
+ * <p>
* 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
* used in {@link android.provider.ContactsContract}.
* 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
* 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
* 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
* 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
* 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.
- * </P>
- * <P>
+ * </p>
+ * <p>
* In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this
* kind of problem (hopefully).
- * </P>
+ * </p>
+ * @hide
*/
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
* appropriate form. Note that "appropriate" is not defined in any vCard specification.
* This is Android-specific.
- * </P>
- * <P>
+ * </p>
+ * <p>
* 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
* 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
* historical reasons, while the vCard importers embedded to softwares for PC should be
* 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
* 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.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Detail:
* How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0.
* </p>
- * <P>
- * e.g.<BR />
- * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR />
- * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR />
- * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR />
- * </P>
- * <P>
- * 2) had been the default of VCard exporter/importer in Android, but it is found that
- * some external exporter is not able to parse the type format like 2) but only 3).
- * </P>
- * <P>
+ * <p>
+ * e.g.
+ * </p>
+ * <ol>
+ * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li>
+ * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li>
+ * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li>
+ * </ol>
+ * <p>
* If you are targeting to the importer which cannot accept TYPE params without "TYPE="
* strings (which should be rare though), please use this flag.
- * </P>
- * <P>
- * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);
- * </P>
+ * </p>
+ * <p>
+ * Example usage:
+ * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre>
+ * </p>
*/
public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000;
/**
- * <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>
+ * <p>
* The flag indicating the vCard composer does touch nothing toward phone number Strings
* but leave it as is.
- * </P>
- * <P>
+ * </p>
+ * <p>
* The vCard specifications mention nothing toward phone numbers, while some devices
* do (wrongly, but with innevitable reasons).
* For example, there's a possibility Japanese mobile phones are expected to have
@@ -207,187 +226,196 @@
* becomes "111-222-3333").
* Unfortunate side effect of that use was some control characters used in the other
* areas may be badly affected by the formatting.
- * </P>
- * <P>
+ * </p>
+ * <p>
* 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.
- * </P>
+ * </p>
*/
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. ////
+ public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000;
+
/**
- * <P>
- * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset.
- * When composing a vCard entry, the US convension will be used toward formatting
- * some values.
- * </P>
- * <P>
+ * <p>
+ * The type indicating nothing. Used by {@link VCardSourceDetector} when it
+ * was not able to guess the exact vCard type.
+ * </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",
* 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 =
- (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V21_GENERIC =
+ (VERSION_21 | NAME_ORDER_DEFAULT | 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.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not fully ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_GENERIC_UTF8 =
- (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_GENERIC =
+ (VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
- /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic";
-
+ /* package */ static final String VCARD_TYPE_V30_GENERIC_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.
* Currently, only name order is considered ("Prefix Middle Given Family Suffix")
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_EUROPE_UTF8 =
- (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
-
- /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe";
+ public static final int VCARD_TYPE_V21_EUROPE =
+ (VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+
+ /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe";
/**
- * <P>
+ * <p>
* 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.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_EUROPE_UTF8 =
- (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_EUROPE =
+ (VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
/* 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.
- * </P>
- * <P>
+ * </p>
+ * <p>
* Not ready yet. Use with caution when you use this.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V21_JAPANESE_UTF8 =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V21_JAPANESE =
+ (VERSION_21 | NAME_ORDER_JAPANESE | 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>
- * 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>
+ * <p>
* 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.
- * </P>
+ * </p>
*/
- public static final int VCARD_TYPE_V30_JAPANESE_UTF8 =
- (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 |
- FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY);
+ public static final int VCARD_TYPE_V30_JAPANESE =
+ (VERSION_30 | NAME_ORDER_JAPANESE | 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
* mobile phones, where phonetic names are translated to half-width katakana if
- * possible, etc.
- * </P>
- * <P>
- * Not ready yet. Use with caution when you use this.
- * </P>
+ * possible, etc. It would be better to use Shift_JIS as a charset for maximum
+ * compatibility.
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_V21_JAPANESE_MOBILE =
- (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS |
- FLAG_CONVERT_PHONETIC_NAME_STRINGS |
- FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
+ (VERSION_21 | NAME_ORDER_JAPANESE |
+ FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES);
/* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile";
/**
- * <P>
- * VCard format used in DoCoMo, which is one of Japanese mobile phone careers.
+ * <p>
+ * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers.
* </p>
- * <P>
+ * <p>
* 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
* are NOT encoded to Quoted-Printable.
- * </P>
+ * </p>
+ * @hide Should not be available world wide.
*/
public static final int VCARD_TYPE_DOCOMO =
(VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_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 Set<Integer> sJapaneseMobileTypeSet;
static {
sVCardTypeMap = new HashMap<String, Integer>();
- sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS);
- sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8);
- 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_GENERIC_STR, VCARD_TYPE_V21_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC);
+ sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE);
+ sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE);
+ sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE);
sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE);
sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO);
sJapaneseMobileTypeSet = new HashSet<Integer>();
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS);
- sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8);
- 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);
+ sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE);
sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO);
}
@@ -404,20 +432,20 @@
}
}
- public static boolean isV30(final int vcardType) {
- return ((vcardType & FLAG_V30) != 0);
+ public static boolean isVersion21(final int vcardType) {
+ 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) {
- return !isV30(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);
+ return !isVersion30(vcardType);
}
public static int getNameOrderType(final int vcardType) {
@@ -442,7 +470,7 @@
}
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 +502,4 @@
private VCardConfig() {
}
-}
+}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java
index 8c07126..76371ef9 100644
--- a/core/java/android/pim/vcard/VCardConstants.java
+++ b/core/java/android/pim/vcard/VCardConstants.java
@@ -21,6 +21,7 @@
public class VCardConstants {
public static final String VERSION_V21 = "2.1";
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.
public static final String PROPERTY_BEGIN = "BEGIN";
@@ -38,25 +39,30 @@
public static final String PROPERTY_PHOTO = "PHOTO";
public static final String PROPERTY_LOGO = "LOGO";
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";
- // 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_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.
- 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.
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_LAST_NAME = "X-PHONETIC-LAST-NAME";
- // Properties both ContactsStruct in Eclair and de-fact vCard extensions
- // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
+ // Properties both ContactsStruct and de-fact vCard extensions
+ // Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
public static final String PROPERTY_X_AIM = "X-AIM";
public static final String PROPERTY_X_MSN = "X-MSN";
public static final String PROPERTY_X_YAHOO = "X-YAHOO";
@@ -89,6 +95,9 @@
public static final String PARAM_TYPE_VOICE = "VOICE";
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.
// We interpret this value as "primary" property during import/export.
//
@@ -109,6 +118,12 @@
public static final String PARAM_TYPE_BBS = "BBS";
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).
// These types are basically encoded to "X-" parameters when composing vCard.
// Parser passes these when "X-" is added to the parameter or not.
@@ -126,14 +141,15 @@
public static final String PARAM_ADR_TYPE_DOM = "DOM";
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.
// Do not use in composer side.
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 static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
// Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,7 +158,14 @@
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;
static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java
index 97d3fbc..94f7c5f 100644
--- a/core/java/android/pim/vcard/VCardEntry.java
+++ b/core/java/android/pim/vcard/VCardEntry.java
@@ -20,13 +20,11 @@
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.OperationApplicationException;
-import android.database.Cursor;
import android.net.Uri;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.Groups;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Event;
@@ -61,9 +59,6 @@
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>();
static {
@@ -78,12 +73,12 @@
Im.PROTOCOL_GOOGLE_TALK);
}
- static public class PhoneData {
+ public static class PhoneData {
public final int type;
public final String data;
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
+ // isPrimary is (not final but) changable, only when there's no appropriate one existing
+ // in the original VCard.
public boolean isPrimary;
public PhoneData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -109,13 +104,11 @@
}
}
- static public class EmailData {
+ public static class EmailData {
public final int type;
public final String data;
// Used only when TYPE is TYPE_CUSTOM.
public final String label;
- // isPrimary is changable only when there's no appropriate one existing in
- // the original VCard.
public boolean isPrimary;
public EmailData(int type, String data, String label, boolean isPrimary) {
this.type = type;
@@ -141,9 +134,9 @@
}
}
- static public class PostalData {
- // Determined by vCard spec.
- // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
+ public static class PostalData {
+ // Determined by vCard specification.
+ // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name
public static final int ADDR_MAX_DATA_SIZE = 7;
private final String[] dataArray;
public final String pobox;
@@ -248,24 +241,28 @@
}
}
- static public class OrganizationData {
+ public static class OrganizationData {
public final int type;
// 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 departmentName;
public String titleName;
+ public final String phoneticName; // We won't have this in "TITLE" property.
public boolean isPrimary;
public OrganizationData(int type,
- String companyName,
- String departmentName,
- String titleName,
- boolean isPrimary) {
+ final String companyName,
+ final String departmentName,
+ final String titleName,
+ final String phoneticName,
+ final boolean isPrimary) {
this.type = type;
this.companyName = companyName;
this.departmentName = departmentName;
this.titleName = titleName;
+ this.phoneticName = phoneticName;
this.isPrimary = isPrimary;
}
@@ -313,7 +310,7 @@
}
}
- static public class ImData {
+ public static class ImData {
public final int protocol;
public final String customProtocol;
public final int type;
@@ -434,6 +431,14 @@
}
}
+ // 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 mGivenName;
private String mMiddleName;
@@ -441,7 +446,7 @@
private String mSuffix;
// Used only when no family nor given name is found.
- private String mFullName;
+ private String mFormattedName;
private String mPhoneticFamilyName;
private String mPhoneticGivenName;
@@ -454,6 +459,7 @@
private String mDisplayName;
private String mBirthday;
+ private String mAnniversary;
private List<String> mNoteList;
private List<PhoneData> mPhoneList;
@@ -469,7 +475,7 @@
private final Account mAccount;
public VCardEntry() {
- this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8);
+ this(VCardConfig.VCARD_TYPE_V21_GENERIC);
}
public VCardEntry(int vcardType) {
@@ -499,7 +505,6 @@
}
}
- // Use NANP in default when there's no information about locale.
final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType);
formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType);
}
@@ -529,22 +534,44 @@
}
/**
- * Should be called via {@link #handleOrgValue(int, List, boolean)} or
+ * Should be called via {@link #handleOrgValue(int, List, Map, boolean) or
* {@link #handleTitleValue(String)}.
*/
private void addNewOrganization(int type, final String companyName,
final String departmentName,
- final String titleName, boolean isPrimary) {
+ final String titleName,
+ final String phoneticName,
+ final boolean isPrimary) {
if (mOrganizationList == null) {
mOrganizationList = new ArrayList<OrganizationData>();
}
mOrganizationList.add(new OrganizationData(type, companyName,
- departmentName, titleName, isPrimary));
+ departmentName, titleName, phoneticName, isPrimary));
}
private static final List<String> sEmptyList =
Collections.unmodifiableList(new ArrayList<String>(0));
+ private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) {
+ final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS);
+ if (sortAsCollection != null && sortAsCollection.size() != 0) {
+ if (sortAsCollection.size() > 1) {
+ Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " +
+ Arrays.toString(sortAsCollection.toArray()));
+ }
+ final List<String> sortNames =
+ VCardUtils.constructListFromValue(sortAsCollection.iterator().next(),
+ mVCardType);
+ final StringBuilder builder = new StringBuilder();
+ for (final String elem : sortNames) {
+ builder.append(elem);
+ }
+ return builder.toString();
+ } else {
+ return null;
+ }
+ }
+
/**
* Set "ORG" related values to the appropriate data. If there's more than one
* {@link OrganizationData} objects, this input data are attached to the last one which
@@ -552,7 +579,9 @@
* {@link OrganizationData} object, a new {@link OrganizationData} is created,
* 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) {
orgList = sEmptyList;
}
@@ -587,7 +616,7 @@
if (mOrganizationList == null) {
// Create new first organization entry, with "null" title which may be
// added via handleTitleValue().
- addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
return;
}
for (OrganizationData organizationData : mOrganizationList) {
@@ -605,7 +634,7 @@
}
// No OrganizatioData is available. Create another one, with "null" title, which may be
// added via handleTitleValue().
- addNewOrganization(type, companyName, departmentName, null, isPrimary);
+ addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary);
}
/**
@@ -619,7 +648,7 @@
if (mOrganizationList == null) {
// Create new first organization entry, with "null" other info, which may be
// added via handleOrgValue().
- addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
return;
}
for (OrganizationData organizationData : mOrganizationList) {
@@ -630,7 +659,7 @@
}
// No Organization is available. Create another one, with "null" other info, which may be
// added via handleOrgValue().
- addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false);
+ addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false);
}
private void addIm(int protocol, String customProtocol, int type,
@@ -656,11 +685,54 @@
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")
- 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)
int size;
- if (elems == null || (size = elems.size()) < 1) {
+ if (paramValues == null || (size = paramValues.size()) < 1) {
return;
}
if (size > 5) {
@@ -668,12 +740,12 @@
}
switch (size) {
- // fallthrough
- case 5: mSuffix = elems.get(4);
- case 4: mPrefix = elems.get(3);
- case 3: mMiddleName = elems.get(2);
- case 2: mGivenName = elems.get(1);
- default: mFamilyName = elems.get(0);
+ // Fall-through.
+ case 5: mSuffix = paramValues.get(4);
+ case 4: mPrefix = paramValues.get(3);
+ case 3: mMiddleName = paramValues.get(2);
+ case 2: mGivenName = paramValues.get(1);
+ default: mFamilyName = paramValues.get(0);
}
}
@@ -754,13 +826,13 @@
if (propName.equals(VCardConstants.PROPERTY_VERSION)) {
// vCard version. Ignore this.
} else if (propName.equals(VCardConstants.PROPERTY_FN)) {
- mFullName = propValue;
- } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) {
+ mFormattedName = propValue;
+ } 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
// actually exist in the real vCard data, does not exist.
- mFullName = propValue;
+ mFormattedName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_N)) {
- handleNProperty(propValueList);
+ handleNProperty(propValueList, paramMap);
} else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) {
mPhoneticFullName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) ||
@@ -775,8 +847,7 @@
// which is correct behavior from the view of vCard 2.1.
// But we want it to be separated, so do the separation here.
final List<String> phoneticNameList =
- VCardUtils.constructListFromValue(propValue,
- VCardConfig.isV30(mVCardType));
+ VCardUtils.constructListFromValue(propValue, mVCardType);
handlePhoneticNameFromSound(phoneticNameList);
} else {
// Ignore this field since Android cannot understand what it is.
@@ -877,7 +948,7 @@
}
}
}
- handleOrgValue(type, propValueList, isPrimary);
+ handleOrgValue(type, propValueList, paramMap, isPrimary);
} else if (propName.equals(VCardConstants.PROPERTY_TITLE)) {
handleTitleValue(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_ROLE)) {
@@ -966,6 +1037,8 @@
mWebsiteList.add(propValue);
} else if (propName.equals(VCardConstants.PROPERTY_BDAY)) {
mBirthday = propValue;
+ } else if (propName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) {
+ mAnniversary = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) {
mPhoneticGivenName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) {
@@ -974,33 +1047,9 @@
mPhoneticFamilyName = propValue;
} else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) {
final List<String> customPropertyList =
- VCardUtils.constructListFromValue(propValue,
- VCardConfig.isV30(mVCardType));
+ VCardUtils.constructListFromValue(propValue, mVCardType);
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 {
- // Unknown X- words and IANA token.
}
}
@@ -1016,8 +1065,8 @@
*/
private void constructDisplayName() {
// FullName (created via "FN" or "NAME" field) is prefered.
- if (!TextUtils.isEmpty(mFullName)) {
- mDisplayName = mFullName;
+ if (!TextUtils.isEmpty(mFormattedName)) {
+ mDisplayName = mFormattedName;
} else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) {
mDisplayName = VCardUtils.constructNameFromElements(mVCardType,
mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix);
@@ -1062,23 +1111,6 @@
if (mAccount != null) {
builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name);
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 {
builder.withValue(RawContacts.ACCOUNT_NAME, null);
builder.withValue(RawContacts.ACCOUNT_TYPE, null);
@@ -1154,6 +1186,9 @@
if (organizationData.titleName != null) {
builder.withValue(Organization.TITLE, organizationData.titleName);
}
+ if (organizationData.phoneticName != null) {
+ builder.withValue(Organization.PHONETIC_NAME, organizationData.phoneticName);
+ }
if (organizationData.isPrimary) {
builder.withValue(Organization.IS_PRIMARY, 1);
}
@@ -1251,6 +1286,15 @@
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) {
for (List<String> customPropertyList : mAndroidCustomPropertyList) {
int size = customPropertyList.size();
@@ -1322,7 +1366,7 @@
&& TextUtils.isEmpty(mGivenName)
&& TextUtils.isEmpty(mPrefix)
&& TextUtils.isEmpty(mSuffix)
- && TextUtils.isEmpty(mFullName)
+ && TextUtils.isEmpty(mFormattedName)
&& TextUtils.isEmpty(mPhoneticFamilyName)
&& TextUtils.isEmpty(mPhoneticMiddleName)
&& TextUtils.isEmpty(mPhoneticGivenName)
@@ -1381,7 +1425,7 @@
}
public String getFullName() {
- return mFullName;
+ return mFormattedName;
}
public String getPhoneticFamilyName() {
diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java
index 59a2baf..a8c8057 100644
--- a/core/java/android/pim/vcard/VCardEntryCommitter.java
+++ b/core/java/android/pim/vcard/VCardEntryCommitter.java
@@ -52,9 +52,9 @@
}
}
- public void onEntryCreated(final VCardEntry contactStruct) {
+ public void onEntryCreated(final VCardEntry vcardEntry) {
long start = System.currentTimeMillis();
- mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver));
+ mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver));
mTimeToCommit += System.currentTimeMillis() - start;
}
diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java
index ae4ec29..aa3e3e2 100644
--- a/core/java/android/pim/vcard/VCardEntryConstructor.java
+++ b/core/java/android/pim/vcard/VCardEntryConstructor.java
@@ -16,78 +16,82 @@
package android.pim.vcard;
import android.accounts.Account;
+import android.text.TextUtils;
+import android.util.Base64;
import android.util.CharsetUtils;
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.Collection;
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 {
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 mCurrentContactStruct;
+ private VCardEntry mCurrentVCardEntry;
private String mParamType;
- /**
- * The charset using which {@link VCardInterpreter} parses the text.
- */
- private String mInputCharset;
+ // 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"
+ // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8).
+ private final String mSourceCharset;
- /**
- * The charset with which byte array is encoded to String.
- */
- final private String mCharsetForDecodedBytes;
- final private boolean mStrictLineBreakParsing;
- final private int mVCardType;
- final private Account mAccount;
+ private final boolean mStrictLineBreaking;
+ private final int mVCardType;
+ private final Account mAccount;
- /** For measuring performance. */
+ // For measuring performance.
private long mTimePushIntoContentResolver;
- final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
+ private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>();
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) {
- this(null, null, false, vcardType, null);
+ this(vcardType, null, null, false);
}
- public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing,
- final int vcardType, final Account account) {
- this(null, charset, strictLineBreakParsing, vcardType, account);
+ public VCardEntryConstructor(final int vcardType, final Account account) {
+ this(vcardType, account, null, false);
}
- public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes,
- final boolean strictLineBreakParsing, final int vcardType,
- final Account account) {
+ public VCardEntryConstructor(final int vcardType, final Account account,
+ final String inputCharset) {
+ 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) {
- mInputCharset = inputCharset;
+ mSourceCharset = inputCharset;
} else {
- mInputCharset = VCardConfig.DEFAULT_CHARSET;
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
}
- if (charsetForDetodedBytes != null) {
- mCharsetForDecodedBytes = charsetForDetodedBytes;
- } else {
- mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES;
- }
- mStrictLineBreakParsing = strictLineBreakParsing;
+ mStrictLineBreaking = strictLineBreakParsing;
mVCardType = vcardType;
mAccount = account;
}
@@ -95,60 +99,63 @@
public void addEntryHandler(VCardEntryHandler entryHandler) {
mEntryHandlers.add(entryHandler);
}
-
+
+ @Override
public void start() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onStart();
}
}
+ @Override
public void end() {
for (VCardEntryHandler entryHandler : mEntryHandlers) {
entryHandler.onEnd();
}
}
- /**
- * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}.
- */
public void clear() {
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
mCurrentProperty = new VCardEntry.Property();
}
- /**
- * Assume that VCard is not nested. In other words, this code does not accept
- */
+ @Override
public void startEntry() {
- if (mCurrentContactStruct != null) {
+ if (mCurrentVCardEntry != null) {
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() {
- mCurrentContactStruct.consolidateFields();
+ mCurrentVCardEntry.consolidateFields();
for (VCardEntryHandler entryHandler : mEntryHandlers) {
- entryHandler.onEntryCreated(mCurrentContactStruct);
+ entryHandler.onEntryCreated(mCurrentVCardEntry);
}
- mCurrentContactStruct = null;
+ mCurrentVCardEntry = null;
}
+ @Override
public void startProperty() {
mCurrentProperty.clear();
}
+ @Override
public void endProperty() {
- mCurrentContactStruct.addProperty(mCurrentProperty);
+ mCurrentVCardEntry.addProperty(mCurrentProperty);
}
+ @Override
public void propertyName(String name) {
mCurrentProperty.setPropertyName(name);
}
+ @Override
public void propertyGroup(String group) {
}
+ @Override
public void propertyParamType(String type) {
if (mParamType != null) {
Log.e(LOG_TAG, "propertyParamType() is called more than once " +
@@ -164,119 +171,34 @@
mParamType = "TYPE";
}
if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
- value = encodeString(value, mCharsetForDecodedBytes);
+ value = VCardUtils.convertStringCharset(
+ value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET);
}
mCurrentProperty.addParameter(mParamType, value);
mParamType = null;
}
- private String encodeString(String originalString, String charsetForDecodedBytes) {
- if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) {
- return originalString;
+ private String handleOneValue(String value,
+ String sourceCharset, String targetCharset, String encoding) {
+ // 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.equals("BASE64") || encoding.equals("B")) {
- mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes()));
+ mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT));
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
- // "= " -> " ", "=\t" -> "\t".
- // Previous code had done this replacement. Keep on the safe side.
- 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);
- }
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreaking, sourceCharset, targetCharset);
}
- // 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) {
@@ -284,24 +206,27 @@
return;
}
- final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET");
- final String charset =
- ((charsetCollection != null) ? charsetCollection.iterator().next() : null);
- final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING");
+ final Collection<String> charsetCollection =
+ mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET);
+ final Collection<String> encodingCollection =
+ mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING);
final String encoding =
((encodingCollection != null) ? encodingCollection.iterator().next() : null);
-
- String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset);
- if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) {
- charsetForDecodedBytes = mCharsetForDecodedBytes;
+ String targetCharset = CharsetUtils.nameForDefaultVendor(
+ ((charsetCollection != null) ? charsetCollection.iterator().next() : null));
+ if (TextUtils.isEmpty(targetCharset)) {
+ targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
for (final String value : values) {
mCurrentProperty.addToPropertyValueList(
- handleOneValue(value, charsetForDecodedBytes, encoding));
+ handleOneValue(value, mSourceCharset, targetCharset, encoding));
}
}
+ /**
+ * @hide
+ */
public void showPerformanceInfo() {
Log.d(LOG_TAG, "time for insert ContactStruct to database: " +
mTimePushIntoContentResolver + " ms");
diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java
index 83a67fe..56bf69d 100644
--- a/core/java/android/pim/vcard/VCardEntryHandler.java
+++ b/core/java/android/pim/vcard/VCardEntryHandler.java
@@ -16,8 +16,13 @@
package android.pim.vcard;
/**
- * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to
- * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}.
+ * <p>
+ * 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 {
/**
diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java
index b5237c0..03704a2 100644
--- a/core/java/android/pim/vcard/VCardInterpreter.java
+++ b/core/java/android/pim/vcard/VCardInterpreter.java
@@ -20,7 +20,7 @@
/**
* <P>
* 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>
* Here, there are several terms specific to vCard (and this library).
diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java
index 99f81f7..77e44a0 100644
--- a/core/java/android/pim/vcard/VCardInterpreterCollection.java
+++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java
@@ -23,9 +23,9 @@
* {@link VCardInterpreter} objects and make a user object treat them as one
* {@link VCardInterpreter} object.
*/
-public class VCardInterpreterCollection implements VCardInterpreter {
+public final class VCardInterpreterCollection implements VCardInterpreter {
private final Collection<VCardInterpreter> mInterpreterCollection;
-
+
public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) {
mInterpreterCollection = interpreterCollection;
}
diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java
index 57c52a6..31b9369 100644
--- a/core/java/android/pim/vcard/VCardParser.java
+++ b/core/java/android/pim/vcard/VCardParser.java
@@ -20,82 +20,36 @@
import java.io.IOException;
import java.io.InputStream;
-public abstract class VCardParser {
- protected final int mParseType;
- protected boolean mCanceled;
-
- public VCardParser() {
- this(VCardConfig.PARSE_TYPE_UNKNOWN);
- }
-
- public VCardParser(int parseType) {
- mParseType = parseType;
- }
-
+public interface VCardParser {
/**
- * <P>
- * Parses the given stream and send the VCard data into VCardBuilderBase object.
- * </P.
- * <P>
+ * <p>
+ * Parses the given stream and send the vCard data into VCardBuilderBase object.
+ * </p>.
+ * <p>
* 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
- * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1,
- * In some exreme case, some VCard may have different charsets in one VCard (though
- * we do not see any device which emits such kind of malicious data)
- * </P>
- * <P>
- * In order to avoid "misunderstanding" charset as much as possible, this method
- * use "ISO-8859-1" for reading the stream. When charset is specified in some property
- * (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>
+ * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1,
+ * In some exreme case, it is allowed for vCard to have different charsets in one vCard.
+ * </p>
+ * <p>
+ * We recommend you 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 interepreter A {@link VCardInterpreter} object which used to construct data.
- * @return Returns true for success. Otherwise returns false.
* @throws IOException, VCardException
*/
- public abstract boolean parse(InputStream is, VCardInterpreter interepreter)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException;
-
+
/**
- * <P>
- * The method variants which accept charset.
- * </P>
- * <P>
- * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use
- * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese
- * 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
+ * <p>
+ * Cancel parsing vCard. Useful when you want to stop the parse in the other threads.
+ * </p>
+ * <p>
+ * Actual cancel is done after parsing the current vcard.
+ * </p>
*/
- public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder)
- 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;
- }
+ public abstract void cancel();
}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java
new file mode 100644
index 0000000..8b365eb
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java
@@ -0,0 +1,1046 @@
+/*
+ * 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.VCardAgentNotSupportedException;
+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.text.TextUtils;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * Basic implementation achieving vCard parsing. Based on vCard 2.1,
+ * </p>
+ * @hide
+ */
+/* package */ class VCardParserImpl_V21 {
+ private static final String LOG_TAG = "VCardParserImpl_V21";
+
+ private static final class EmptyInterpreter implements VCardInterpreter {
+ @Override
+ public void end() {
+ }
+ @Override
+ public void endEntry() {
+ }
+ @Override
+ public void endProperty() {
+ }
+ @Override
+ public void propertyGroup(String group) {
+ }
+ @Override
+ public void propertyName(String name) {
+ }
+ @Override
+ public void propertyParamType(String type) {
+ }
+ @Override
+ public void propertyParamValue(String value) {
+ }
+ @Override
+ public void propertyValues(List<String> values) {
+ }
+ @Override
+ public void start() {
+ }
+ @Override
+ public void startEntry() {
+ }
+ @Override
+ public void startProperty() {
+ }
+ }
+
+ protected static final class CustomBufferedReader extends BufferedReader {
+ private long mTime;
+
+ /**
+ * Needed since "next line" may be null due to end of line.
+ */
+ private boolean mNextLineIsValid;
+ private String mNextLine;
+
+ public CustomBufferedReader(Reader in) {
+ super(in);
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ if (mNextLineIsValid) {
+ final String ret = mNextLine;
+ mNextLine = null;
+ mNextLineIsValid = false;
+ return ret;
+ }
+
+ long start = System.currentTimeMillis();
+ final String line = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+ return line;
+ }
+
+ /**
+ * Read one line, but make this object store it in its queue.
+ */
+ public String peekLine() throws IOException {
+ if (!mNextLineIsValid) {
+ long start = System.currentTimeMillis();
+ final String line = super.readLine();
+ long end = System.currentTimeMillis();
+ mTime += end - start;
+
+ mNextLine = line;
+ mNextLineIsValid = true;
+ }
+
+ return mNextLine;
+ }
+
+ public long getTotalmillisecond() {
+ return mTime;
+ }
+ }
+
+ private static final String DEFAULT_ENCODING = "8BIT";
+
+ protected boolean mCanceled;
+ protected VCardInterpreter mInterpreter;
+
+ protected final String mIntermediateCharset;
+
+ /**
+ * <p>
+ * The encoding type for deconding byte streams. This member variable is
+ * reset to a default encoding every time when a new item comes.
+ * </p>
+ * <p>
+ * "Encoding" in vCard is different from "Charset". It is mainly used for
+ * addresses, notes, images. "7BIT", "8BIT", "BASE64", and
+ * "QUOTED-PRINTABLE" are known examples.
+ * </p>
+ */
+ protected String mCurrentEncoding;
+
+ /**
+ * <p>
+ * The reader object to be used internally.
+ * </p>
+ * <p>
+ * Developers should not directly read a line from this object. Use
+ * getLine() unless there some reason.
+ * </p>
+ */
+ protected CustomBufferedReader mReader;
+
+ /**
+ * <p>
+ * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard
+ * specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownTypeSet = new HashSet<String>();
+
+ /**
+ * <p>
+ * Set for storing unkonwn VALUE attributes, which is not acceptable in
+ * vCard specification, but happens to be seen in real world vCard.
+ * </p>
+ */
+ protected final Set<String> mUnknownValueSet = new HashSet<String>();
+
+
+ // 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.
+ // TODO: Don't ignore by using count, but read all of information outside vCard.
+ private int mNestCount;
+
+ // Used only for parsing END:VCARD.
+ private String mPreviousLine;
+
+ // 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 VCardParserImpl_V21() {
+ this(VCardConfig.VCARD_TYPE_DEFAULT);
+ }
+
+ public VCardParserImpl_V21(int vcardType) {
+ if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) {
+ mNestCount = 1;
+ }
+
+ mIntermediateCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
+ }
+
+ /**
+ * <p>
+ * Parses the file at the given position.
+ * </p>
+ */
+ // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre>
+ protected void parseVCardFile() throws IOException, VCardException {
+ boolean readingFirstFile = true;
+ while (true) {
+ if (mCanceled) {
+ break;
+ }
+ if (!parseOneVCard(readingFirstFile)) {
+ break;
+ }
+ readingFirstFile = false;
+ }
+
+ if (mNestCount > 0) {
+ boolean useCache = true;
+ for (int i = 0; i < mNestCount; i++) {
+ readEndVCard(useCache, true);
+ useCache = false;
+ }
+ }
+ }
+
+ /**
+ * @return true when a given property name is a valid property name.
+ */
+ protected boolean isValidPropertyName(final String propertyName) {
+ if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) ||
+ propertyName.startsWith("X-"))
+ && !mUnknownTypeSet.contains(propertyName)) {
+ mUnknownTypeSet.add(propertyName);
+ Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName);
+ }
+ return true;
+ }
+
+ /**
+ * @return String. It may be null, or its length may be 0
+ * @throws IOException
+ */
+ protected String getLine() throws IOException {
+ return mReader.readLine();
+ }
+
+ protected String peekLine() throws IOException {
+ return mReader.peekLine();
+ }
+
+ /**
+ * @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 firstRead) throws IOException, VCardException {
+ boolean allowGarbage = false;
+ if (firstRead) {
+ if (mNestCount > 0) {
+ for (int i = 0; i < mNestCount; i++) {
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ allowGarbage = true;
+ }
+ }
+ }
+
+ if (!readBeginVCard(allowGarbage)) {
+ return false;
+ }
+ final long beforeStartEntry = System.currentTimeMillis();
+ mInterpreter.startEntry();
+ mTimeReadStartRecord += System.currentTimeMillis() - beforeStartEntry;
+
+ final long beforeParseItems = System.currentTimeMillis();
+ parseItems();
+ mTimeParseItems += System.currentTimeMillis() - beforeParseItems;
+
+ readEndVCard(true, false);
+
+ final long beforeEndEntry = System.currentTimeMillis();
+ mInterpreter.endEntry();
+ mTimeReadEndRecord += System.currentTimeMillis() - beforeEndEntry;
+ 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;
+ }
+ }
+ final String[] strArray = line.split(":", 2);
+ final int length = strArray.length;
+
+ // Although vCard 2.1/3.0 specification does not allow lower cases,
+ // we found 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.");
+ }
+
+ /**
+ * <p>
+ * The arguments useCache and allowGarbase are usually true and false
+ * accordingly when this function is called outside this function itself.
+ * </p>
+ *
+ * @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 {
+ String line;
+ 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;
+
+ final long beforeBeginProperty = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - beforeBeginProperty;
+ ended = parseItem();
+ if (!ended) {
+ final long beforeEndProperty = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
+ }
+
+ while (!ended) {
+ final long beforeStartProperty = System.currentTimeMillis();
+ mInterpreter.startProperty();
+ mTimeStartProperty += System.currentTimeMillis() - beforeStartProperty;
+ try {
+ ended = parseItem();
+ } catch (VCardInvalidCommentLineException e) {
+ Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+ ended = false;
+ }
+
+ if (!ended) {
+ final long beforeEndProperty = System.currentTimeMillis();
+ mInterpreter.endProperty();
+ mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty;
+ }
+ }
+ }
+
+ /*
+ * 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 {
+ mCurrentEncoding = DEFAULT_ENCODING;
+
+ 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 + "\"");
+ }
+
+ // For performance reason, the states for group and property name are merged into one.
+ static private final int STATE_GROUP_OR_PROPERTY_NAME = 0;
+ static private final int STATE_PARAMS = 1;
+ // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not.
+ static private final int STATE_PARAMS_IN_DQUOTE = 2;
+
+ protected String[] separateLineAndHandleGroup(String line) throws VCardException {
+ final String[] propertyNameAndValue = new String[2];
+ final int length = line.length();
+ if (length > 0 && line.charAt(0) == '#') {
+ throw new VCardInvalidCommentLineException();
+ }
+
+ int state = STATE_GROUP_OR_PROPERTY_NAME;
+ int nameIndex = 0;
+
+ // This loop is developed so that we don't have to take care of bottle neck here.
+ // Refactor carefully when you need to do so.
+ for (int i = 0; i < length; i++) {
+ final char ch = line.charAt(i);
+ switch (state) {
+ case STATE_GROUP_OR_PROPERTY_NAME: {
+ if (ch == ':') { // End of a property name.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ mInterpreter.propertyName(propertyName);
+ propertyNameAndValue[0] = propertyName;
+ if (i < length - 1) {
+ propertyNameAndValue[1] = line.substring(i + 1);
+ } else {
+ propertyNameAndValue[1] = "";
+ }
+ return propertyNameAndValue;
+ } else if (ch == '.') { // Each group is followed by the dot.
+ final String groupName = line.substring(nameIndex, i);
+ if (groupName.length() == 0) {
+ Log.w(LOG_TAG, "Empty group found. Ignoring.");
+ } else {
+ mInterpreter.propertyGroup(groupName);
+ }
+ nameIndex = i + 1; // Next should be another group or a property name.
+ } else if (ch == ';') { // End of property name and beginneng of parameters.
+ final String propertyName = line.substring(nameIndex, i);
+ if (propertyName.equalsIgnoreCase("END")) {
+ mPreviousLine = line;
+ return null;
+ }
+ mInterpreter.propertyName(propertyName);
+ propertyNameAndValue[0] = propertyName;
+ nameIndex = i + 1;
+ state = STATE_PARAMS; // Start parameter parsing.
+ }
+ // TODO: comma support (in vCard 3.0 and 4.0).
+ break;
+ }
+ case STATE_PARAMS: {
+ if (ch == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ state = STATE_PARAMS_IN_DQUOTE;
+ } else if (ch == ';') { // Starts another param.
+ handleParams(line.substring(nameIndex, i));
+ nameIndex = i + 1;
+ } else if (ch == ':') { // End of param and beginenning of values.
+ 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 == '"') {
+ if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) {
+ Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " +
+ "Silently allow it");
+ }
+ 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 {
+ final 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 implementation 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) {
+ if (!(getKnownTypeSet().contains(ptypeval.toUpperCase())
+ || ptypeval.startsWith("X-"))
+ && !mUnknownTypeSet.contains(ptypeval)) {
+ mUnknownTypeSet.add(ptypeval);
+ Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval));
+ }
+ mInterpreter.propertyParamType("TYPE");
+ mInterpreter.propertyParamValue(ptypeval);
+ }
+
+ /*
+ * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
+ */
+ protected void handleValue(final String pvalueval) {
+ if (!(getKnownValueSet().contains(pvalueval.toUpperCase())
+ || pvalueval.startsWith("X-")
+ || mUnknownValueSet.contains(pvalueval))) {
+ mUnknownValueSet.add(pvalueval);
+ Log.w(LOG_TAG, String.format(
+ "The value unsupported by TYPE of %s: ", getVersion(), pvalueval));
+ }
+ mInterpreter.propertyParamType("VALUE");
+ mInterpreter.propertyParamValue(pvalueval);
+ }
+
+ /*
+ * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word
+ */
+ protected void handleEncoding(String pencodingval) throws VCardException {
+ if (getAvailableEncodingSet().contains(pencodingval) ||
+ pencodingval.startsWith("X-")) {
+ mInterpreter.propertyParamType("ENCODING");
+ mInterpreter.propertyParamValue(pencodingval);
+ mCurrentEncoding = pencodingval;
+ } else {
+ throw new VCardException("Unknown encoding \"" + pencodingval + "\"");
+ }
+ }
+
+ /**
+ * <p>
+ * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521),
+ * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc.
+ * We allow any charset.
+ * </p>
+ */
+ protected void handleCharset(String charsetval) {
+ mInterpreter.propertyParamType("CHARSET");
+ mInterpreter.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 (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ tmp = strArray[1];
+ length = tmp.length();
+ for (int i = 0; i < length; i++) {
+ if (!isAsciiLetter(tmp.charAt(i))) {
+ throw new VCardException("Invalid Language: \"" + langval + "\"");
+ }
+ }
+ mInterpreter.propertyParamType(VCardConstants.PARAM_LANGUAGE);
+ mInterpreter.propertyParamValue(langval);
+ }
+
+ private boolean isAsciiLetter(char ch) {
+ if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Mainly for "X-" type. This accepts any kind of type without check.
+ */
+ protected void handleAnyParam(String paramName, String paramValue) {
+ mInterpreter.propertyParamType(paramName);
+ mInterpreter.propertyParamValue(paramValue);
+ }
+
+ protected void handlePropertyValue(String propertyName, String propertyValue)
+ throws IOException, VCardException {
+ final String upperEncoding = mCurrentEncoding.toUpperCase();
+ if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) {
+ final long start = System.currentTimeMillis();
+ final String result = getQuotedPrintable(propertyValue);
+ final ArrayList<String> v = new ArrayList<String>();
+ v.add(result);
+ mInterpreter.propertyValues(v);
+ mTimeHandleQuotedPrintable += System.currentTimeMillis() - start;
+ } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64)
+ || upperEncoding.equals(VCardConstants.PARAM_ENCODING_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 ArrayList<String> arrayList = new ArrayList<String>();
+ arrayList.add(getBase64(propertyValue));
+ mInterpreter.propertyValues(arrayList);
+ } catch (OutOfMemoryError error) {
+ Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!");
+ mInterpreter.propertyValues(null);
+ }
+ mTimeHandleBase64 += System.currentTimeMillis() - start;
+ } else {
+ if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") ||
+ upperEncoding.startsWith("X-"))) {
+ Log.w(LOG_TAG,
+ String.format("The encoding \"%s\" is unsupported by vCard %s",
+ mCurrentEncoding, getVersionString()));
+ }
+
+ // Some device uses line folding defined in RFC 2425, which is not allowed
+ // in vCard 2.1 (while needed in vCard 3.0).
+ //
+ // e.g.
+ // BEGIN:VCARD
+ // VERSION:2.1
+ // N:;Omega;;;
+ // EMAIL;INTERNET:"Omega"
+ // <omega@example.com>
+ // FN:Omega
+ // END:VCARD
+ //
+ // The vCard above assumes that email address should become:
+ // "Omega" <omega@example.com>
+ //
+ // But vCard 2.1 requires Quote-Printable when a line contains line break(s).
+ //
+ // For more information about line folding,
+ // see "5.8.1. Line delimiting and folding" in RFC 2425.
+ //
+ // We take care of this case more formally in vCard 3.0, so we only need to
+ // do this in vCard 2.1.
+ if (getVersion() == VCardConfig.VERSION_21) {
+ StringBuilder builder = null;
+ while (true) {
+ final String nextLine = peekLine();
+ // We don't need to care too much about this exceptional case,
+ // but we should not wrongly eat up "END:VCARD", since it critically
+ // breaks this parser's state machine.
+ // Thus we roughly look over the next line and confirm it is at least not
+ // "END:VCARD". This extra fee is worth paying. This is exceptional
+ // anyway.
+ if (!TextUtils.isEmpty(nextLine) &&
+ nextLine.charAt(0) == ' ' &&
+ !"END:VCARD".contains(nextLine.toUpperCase())) {
+ getLine(); // Drop the next line.
+
+ if (builder == null) {
+ builder = new StringBuilder();
+ builder.append(propertyValue);
+ }
+ builder.append(nextLine.substring(1));
+ } else {
+ break;
+ }
+ }
+ if (builder != null) {
+ propertyValue = builder.toString();
+ }
+ }
+
+ final long start = System.currentTimeMillis();
+ ArrayList<String> v = new ArrayList<String>();
+ v.add(maybeUnescapeText(propertyValue));
+ mInterpreter.propertyValues(v);
+ mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start;
+ }
+ }
+
+ /**
+ * <p>
+ * Parses and returns Quoted-Printable.
+ * </p>
+ *
+ * @param firstString The string following a parameter name and attributes.
+ * Example: "string" in
+ * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r".
+ * @return whole Quoted-Printable string, including a given argument and
+ * following lines. Excludes the last empty line following to Quoted
+ * Printable lines.
+ * @throws IOException
+ * @throws VCardException
+ */
+ private 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 a 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();
+ }
+
+ /**
+ * <p>
+ * Mainly for "ADR", "ORG", and "N"
+ * </p>
+ */
+ /*
+ * 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
+ * do not care the number of "strnosemi" here. We are not sure whether we
+ * should add "\" CRLF to each value. We exclude them for now.
+ */
+ 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 (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ propertyValue = getQuotedPrintable(propertyValue);
+ }
+
+ mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue,
+ getVersion()));
+ }
+
+ /*
+ * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an
+ * error toward the AGENT property.
+ * // TODO: Support AGENT property.
+ * 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.");
+ }
+ }
+
+ /**
+ * 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);
+ }
+
+ /* package */ 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;
+ }
+ }
+
+ private void showPerformanceInfo() {
+ Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms");
+ Log.d(LOG_TAG, "Total readLine time: " + 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");
+ }
+
+ /**
+ * @return {@link VCardConfig#VERSION_21}
+ */
+ protected int getVersion() {
+ return VCardConfig.VERSION_21;
+ }
+
+ /**
+ * @return {@link VCardConfig#VERSION_30}
+ */
+ protected String getVersionString() {
+ return VCardConstants.VERSION_V21;
+ }
+
+ protected Set<String> getKnownPropertyNameSet() {
+ return VCardParser_V21.sKnownPropertyNameSet;
+ }
+
+ protected Set<String> getKnownTypeSet() {
+ return VCardParser_V21.sKnownTypeSet;
+ }
+
+ protected Set<String> getKnownValueSet() {
+ return VCardParser_V21.sKnownValueSet;
+ }
+
+ protected Set<String> getAvailableEncodingSet() {
+ return VCardParser_V21.sAvailableEncoding;
+ }
+
+ protected String getDefaultEncoding() {
+ return DEFAULT_ENCODING;
+ }
+
+
+ public void parse(InputStream is, VCardInterpreter interpreter)
+ throws IOException, VCardException {
+ if (is == null) {
+ throw new NullPointerException("InputStream must not be null.");
+ }
+
+ final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset);
+ mReader = new CustomBufferedReader(tmpReader);
+
+ mInterpreter = (interpreter != null ? interpreter : new EmptyInterpreter());
+
+ final long start = System.currentTimeMillis();
+ if (mInterpreter != null) {
+ mInterpreter.start();
+ }
+ parseVCardFile();
+ if (mInterpreter != null) {
+ mInterpreter.end();
+ }
+ mTimeTotal += System.currentTimeMillis() - start;
+
+ if (VCardConfig.showPerformanceLog()) {
+ showPerformanceInfo();
+ }
+ }
+
+ public final void cancel() {
+ mCanceled = true;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java
new file mode 100644
index 0000000..3fad9a0
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java
@@ -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;
+ }
+}
diff --git a/core/java/android/pim/vcard/VCardParserImpl_V40.java b/core/java/android/pim/vcard/VCardParserImpl_V40.java
new file mode 100644
index 0000000..0fe76bb
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParserImpl_V40.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index fe8cfb0..507a176 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -15,922 +15,95 @@
*/
package android.pim.vcard;
-import android.pim.vcard.exception.VCardAgentNotSupportedException;
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.InputStream;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
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 {
- private static final String LOG_TAG = "VCardParser_V21";
-
- /** Store the known-type */
- private static final HashSet<String> sKnownTypeSet = 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"));
-
- /** 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"));
+public final class VCardParser_V21 implements VCardParser {
+ /**
+ * A unmodifiable Set storing the property names available in the vCard 2.1 specification.
+ */
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(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.
- * We allow it for safety...
+ * We allow it for safety.
+ * </p>
*/
- private static final HashSet<String> sAvailableEncodingV21 =
- new HashSet<String>(Arrays.asList(
- "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B"));
-
- // Used only for parsing END:VCARD.
- private String mPreviousLine;
-
- /** The builder to build parsed data */
- protected VCardInterpreter mBuilder = null;
+ /* package */ static final Set<String> sAvailableEncoding =
+ Collections.unmodifiableSet(new HashSet<String>(
+ Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT,
+ VCardConstants.PARAM_ENCODING_8BIT,
+ VCardConstants.PARAM_ENCODING_QP,
+ VCardConstants.PARAM_ENCODING_BASE64,
+ VCardConstants.PARAM_ENCODING_B)));
- /**
- * 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;
+ private final VCardParserImpl_V21 mVCardParserImpl;
public VCardParser_V21() {
- this(null);
+ mVCardParserImpl = new VCardParserImpl_V21();
}
- public VCardParser_V21(VCardSourceDetector detector) {
- this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN);
+ public VCardParser_V21(int vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V21(vcardType);
}
- public VCardParser_V21(int parseType) {
- 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)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- String line;
- 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 + "\"");
+ mVCardParserImpl.parse(is, interepreter);
}
- static private final int STATE_GROUP_OR_PROPNAME = 0;
- static private final int STATE_PARAMS = 1;
- // 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;
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java
index 4ecfe97..238d3a8 100644
--- a/core/java/android/pim/vcard/VCardParser_V30.java
+++ b/core/java/android/pim/vcard/VCardParser_V30.java
@@ -16,343 +16,72 @@
package android.pim.vcard;
import android.pim.vcard.exception.VCardException;
-import android.util.Log;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Set;
/**
- * The class used to parse vCard 3.0.
- * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426).
+ * <p>
+ * 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 {
- private static final String LOG_TAG = "VCardParser_V30";
-
- private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>(
- Arrays.asList(
+public class VCardParser_V30 implements VCardParser {
+ /* package */ static final Set<String> sKnownPropertyNameSet =
+ Collections.unmodifiableSet(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", // 2.1
"NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS",
- "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;
+ "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0
/**
- * True when the caller wants the parser to be strict about the input.
- * Currently this is only for testing.
+ * <p>
+ * 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() {
- super();
- mStrictParsing = false;
+ mVCardParserImpl = new VCardParserImpl_V30();
}
- /**
- * @param strictParsing when true, this object throws VCardException when the vcard is not
- * 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 vcardType) {
+ mVCardParserImpl = new VCardParserImpl_V30(vcardType);
}
- public VCardParser_V30(int parseMode) {
- 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)
+ public void parse(InputStream is, VCardInterpreter interepreter)
throws IOException, VCardException {
- // TODO: vCard 3.0 supports group.
- super.readEndVCard(useCache, allowGarbage);
+ mVCardParserImpl.parse(is, interepreter);
}
- /**
- * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not.
- */
- @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);
- }
+ public void cancel() {
+ mVCardParserImpl.cancel();
}
}
diff --git a/core/java/android/pim/vcard/VCardParser_V40.java b/core/java/android/pim/vcard/VCardParser_V40.java
new file mode 100644
index 0000000..65a2f68
--- /dev/null
+++ b/core/java/android/pim/vcard/VCardParser_V40.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java
index 7297c50..4c6461e 100644
--- a/core/java/android/pim/vcard/VCardSourceDetector.java
+++ b/core/java/android/pim/vcard/VCardSourceDetector.java
@@ -15,17 +15,33 @@
*/
package android.pim.vcard;
+import android.text.TextUtils;
+import android.util.Log;
+
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
- * Class which tries to detects the source of the vCard from its properties.
- * Currently this implementation is very premature.
- * @hide
+ * <p>
+ * The class which tries to detects the source of a vCard file from its contents.
+ * </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 {
+ private static final String LOG_TAG = "VCardSourceDetector";
+
private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList(
"X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME",
"X-ABADR", "X-ABUID"));
@@ -42,10 +58,30 @@
"X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED",
"X-SD-DESCRIPTION"));
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.
- private boolean mNeedParseSpecifiedCharset;
+ private boolean mNeedToParseCharset;
private String mSpecifiedCharset;
public void start() {
@@ -58,7 +94,8 @@
}
public void startProperty() {
- mNeedParseSpecifiedCharset = false;
+ mNeedToParseCharset = false;
+ mNeedToParseVersion = false;
}
public void endProperty() {
@@ -71,22 +108,26 @@
}
public void propertyName(String name) {
- if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
- mNeedParseSpecifiedCharset = true;
+ if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) {
+ mNeedToParseVersion = 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;
}
- if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) {
+ if (mParseType != PARSE_TYPE_UNKNOWN) {
return;
}
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)) {
- mType = VCardConfig.PARSE_TYPE_FOMA;
+ mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST;
} 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)) {
- mType = VCardConfig.PARSE_TYPE_APPLE;
+ mParseType = PARSE_TYPE_APPLE;
}
}
@@ -97,30 +138,65 @@
}
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);
}
}
- /* 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).
+ * </p>
* @return Charset String. Null is returned if guessing the source fails.
*/
public String getEstimatedCharset() {
- if (mSpecifiedCharset != null) {
+ if (TextUtils.isEmpty(mSpecifiedCharset)) {
return mSpecifiedCharset;
}
- switch (mType) {
- case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP:
- case VCardConfig.PARSE_TYPE_FOMA:
- case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP:
+ switch (mParseType) {
+ case PARSE_TYPE_WINDOWS_MOBILE_V65_JP:
+ case PARSE_TYPE_DOCOMO_TORELATE_NEST:
+ case PARSE_TYPE_MOBILE_PHONE_JP:
return "SHIFT_JIS";
- case VCardConfig.PARSE_TYPE_APPLE:
+ case PARSE_TYPE_APPLE:
return "UTF-8";
default:
return null;
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index f972799..abceca0 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -16,13 +16,21 @@
package android.pim.vcard;
import android.content.ContentProviderOperation;
+import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
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.Arrays;
import java.util.Collection;
@@ -36,6 +44,8 @@
* Utilities for VCard handling codes.
*/
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
// converted to two parameter Strings. These only contain some minor fields valid in both
// vCard and current (as of 2009-08-07) Contacts structure.
@@ -185,8 +195,7 @@
// For backward compatibility.
// 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.
- return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label)
- || sMobilePhoneLabelSet.contains(label));
+ return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label));
}
public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) {
@@ -240,10 +249,13 @@
}
/**
+ * <p>
* Inserts postal data into the builder object.
- *
+ * </p>
+ * <p>
* Note that the data structure of ContactsContract is different from that defined in vCard.
* So some conversion may be performed in this method.
+ * </p>
*/
public static void insertStructuredPostalDataUsingContactsStruct(int vcardType,
final ContentProviderOperation.Builder builder,
@@ -319,18 +331,33 @@
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,
- final boolean isV30) {
+ final int vcardType) {
final List<String> list = new ArrayList<String>();
StringBuilder builder = new StringBuilder();
- int length = value.length();
+ 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);
- final String unescapedString =
- (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) :
- VCardParser_V21.unescapeCharacter(nextCh));
+ final String unescapedString;
+ if (VCardConfig.isVersion40(vcardType)) {
+ 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) {
builder.append(unescapedString);
i++;
@@ -371,9 +398,13 @@
}
/**
+ * <p>
* This is useful when checking the string should be encoded into quoted-printable
* or not, which is required by vCard 2.1.
+ * </p>
+ * <p>
* See the definition of "7bit" in vCard 2.1 spec for more information.
+ * </p>
*/
public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) {
if (values == null) {
@@ -407,13 +438,16 @@
new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' '));
/**
+ * <p>
* This is useful since vCard 3.0 often requires the ("X-") properties and groups
* should contain only alphabets, digits, and hyphen.
- *
+ * </p>
+ * <p>
* 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
* 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) {
if (values == null) {
@@ -451,14 +485,39 @@
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.
- * </P>
- * <P>
- * vCard 2.1 specifies:<BR />
+ * </p>
+ * <p>
+ * vCard 2.1 specifies:<br />
* word = <any printable 7bit us-ascii except []=:., >
- * </P>
+ * </p>
*/
public static boolean isV21Word(final String value) {
if (TextUtils.isEmpty(value)) {
@@ -477,6 +536,14 @@
return true;
}
+ private static final int[] sEscapeIndicatorsV30 = new int[]{
+ ':', ';', ',', ' '
+ };
+
+ private static final int[] sEscapeIndicatorsV40 = new int[]{
+ ';', ':'
+ };
+
/**
* <P>
* Returns String available as parameter value in vCard 3.0.
@@ -487,10 +554,18 @@
* This method checks whether the given String can be used without quotes.
* </P>
* <P>
- * Note: We remove DQUOTE silently for now.
+ * Note: We remove DQUOTE inside the given value silently for now.
* </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)) {
value = "";
}
@@ -506,12 +581,19 @@
continue;
}
builder.appendCodePoint(codePoint);
- if (codePoint == ':' || codePoint == ',' || codePoint == ' ') {
- needQuote = true;
+ for (int indicator : escapeIndicators) {
+ if (codePoint == indicator) {
+ needQuote = true;
+ break;
+ }
}
}
+
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) {
@@ -577,6 +659,138 @@
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() {
}
}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
index 330153e..b80584b 100644
--- a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
+++ b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard.exception;
/**
diff --git a/core/java/android/pim/vcard/exception/VCardVersionException.java b/core/java/android/pim/vcard/exception/VCardVersionException.java
index 9fe8b7f..0709fe4 100644
--- a/core/java/android/pim/vcard/exception/VCardVersionException.java
+++ b/core/java/android/pim/vcard/exception/VCardVersionException.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard.exception;
/**
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index aa9fe76..4d2ba71 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -143,12 +143,12 @@
/**
* Max distance to overscroll for edge effects
*/
- private static final int OVERSCROLL_DISTANCE = 4;
+ private static final int OVERSCROLL_DISTANCE = 2;
/**
* Max distance to overfling for edge effects
*/
- private static final int OVERFLING_DISTANCE = 8;
+ private static final int OVERFLING_DISTANCE = 4;
private final int mEdgeSlop;
private final int mFadingEdgeLength;
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 4e57a8a..da0c61b 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -1054,6 +1054,7 @@
* @deprecated This method is no longer used as plugins are loaded from
* their own APK via the system's package manager.
*/
+ @Deprecated
public synchronized void setPluginsPath(String pluginsPath) {
}
@@ -1229,6 +1230,7 @@
* @deprecated This method is no longer used as plugins are loaded from
* their own APK via the system's package manager.
*/
+ @Deprecated
public synchronized String getPluginsPath() {
return "";
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 7898083..c01068f 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1041,7 +1041,7 @@
if (mode != OVERSCROLL_NEVER) {
if (mEdgeGlowTop == null) {
final Resources res = getContext().getResources();
- final Drawable edge = res.getDrawable(R.drawable.edge_light);
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
mEdgeGlowTop = new EdgeGlow(edge, glow);
mEdgeGlowBottom = new EdgeGlow(edge, glow);
@@ -2567,11 +2567,6 @@
mInOverScrollMode = true;
}
- if ((clampedX && maxX > 0) || clampedY) {
- // Hitting a scroll barrier breaks velocity; don't fling further.
- mVelocityTracker.clear();
- mLastVelocity = 0;
- }
super.scrollTo(scrollX, scrollY);
}
@@ -3469,8 +3464,8 @@
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- super.dispatchDraw(canvas);
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) {
invalidate();
}
@@ -3492,7 +3487,7 @@
if (!mEdgeGlowTop.isFinished()) {
final int restoreCount = canvas.save();
- canvas.translate(-width / 2 + scrollX, scrollY);
+ canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY));
mEdgeGlowTop.setSize(width * 2, height);
invalidateForGlow |= mEdgeGlowTop.draw(canvas);
canvas.restoreToCount(restoreCount);
@@ -3500,7 +3495,7 @@
if (!mEdgeGlowBottom.isFinished()) {
final int restoreCount = canvas.save();
- canvas.translate(-width / 2 - scrollX, scrollY + height);
+ canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height);
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width * 2, height);
invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
@@ -3510,7 +3505,7 @@
final int restoreCount = canvas.save();
canvas.rotate(270);
- canvas.translate(-height * 1.5f - scrollY, scrollX);
+ canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX));
mEdgeGlowLeft.setSize(height * 2, width);
invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
canvas.restoreToCount(restoreCount);
@@ -3519,7 +3514,8 @@
final int restoreCount = canvas.save();
canvas.rotate(90);
- canvas.translate(-height / 2 + scrollY, -scrollX - width);
+ canvas.translate(-height / 2 + scrollY,
+ -(Math.max(computeMaxScrollX(), scrollX) + width));
mEdgeGlowRight.setSize(height * 2, width);
invalidateForGlow |= mEdgeGlowRight.draw(canvas);
canvas.restoreToCount(restoreCount);
@@ -5977,6 +5973,24 @@
+ " maxX=" + maxX + " maxY=" + maxY
+ " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
}
+
+ // Allow sloppy flings without overscrolling at the edges.
+ if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
+ vx = 0;
+ }
+ if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
+ vy = 0;
+ }
+
+ if (mOverscrollDistance < mOverflingDistance) {
+ if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) {
+ vx = 0;
+ }
+ if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) {
+ vy = 0;
+ }
+ }
+
mLastVelX = vx;
mLastVelY = vy;
mLastVelocity = (float) Math.hypot(vx, vy);
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index db50ca1..fe2a43b 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -507,6 +507,20 @@
private EdgeGlow mEdgeGlowBottom;
/**
+ * An estimate of how many pixels are between the top of the list and
+ * the top of the first position in the adapter, based on the last time
+ * we saw it. Used to hint where to draw edge glows.
+ */
+ private int mFirstPositionDistanceGuess;
+
+ /**
+ * An estimate of how many pixels are between the bottom of the list and
+ * the bottom of the last position in the adapter, based on the last time
+ * we saw it. Used to hint where to draw edge glows.
+ */
+ private int mLastPositionDistanceGuess;
+
+ /**
* Interface definition for a callback to be invoked when the list or grid
* has been scrolled.
*/
@@ -632,7 +646,7 @@
if (mode != OVERSCROLL_NEVER) {
if (mEdgeGlowTop == null) {
final Resources res = getContext().getResources();
- final Drawable edge = res.getDrawable(R.drawable.edge_light);
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
mEdgeGlowTop = new EdgeGlow(edge, glow);
mEdgeGlowBottom = new EdgeGlow(edge, glow);
@@ -1684,6 +1698,7 @@
mFlingRunnable.endFling();
if (mScrollY != 0) {
mScrollY = 0;
+ finishGlows();
invalidate();
}
}
@@ -2041,6 +2056,7 @@
if (mScrollY != 0) {
mScrollY = 0;
+ finishGlows();
invalidate();
}
}
@@ -2518,7 +2534,7 @@
final int restoreCount = canvas.save();
final int width = getWidth();
- canvas.translate(-width / 2, scrollY);
+ canvas.translate(-width / 2, Math.min(0, scrollY + mFirstPositionDistanceGuess));
mEdgeGlowTop.setSize(width * 2, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
invalidate();
@@ -2530,7 +2546,8 @@
final int width = getWidth();
final int height = getHeight();
- canvas.translate(-width / 2, scrollY + height);
+ canvas.translate(-width / 2,
+ Math.max(height, scrollY + mLastPositionDistanceGuess));
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width * 2, height);
if (mEdgeGlowBottom.draw(canvas)) {
@@ -3223,6 +3240,18 @@
final int firstPosition = mFirstPosition;
+ // Update our guesses for where the first and last views are
+ if (firstPosition == 0) {
+ mFirstPositionDistanceGuess = firstTop - mListPadding.top;
+ } else {
+ mFirstPositionDistanceGuess += incrementalDeltaY;
+ }
+ if (firstPosition + childCount == mItemCount) {
+ mLastPositionDistanceGuess = lastBottom + mListPadding.bottom;
+ } else {
+ mLastPositionDistanceGuess += incrementalDeltaY;
+ }
+
if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
// Don't need to move views down if the top of the first position
// is already visible
@@ -4167,6 +4196,13 @@
return result;
}
+ private void finishGlows() {
+ if (mEdgeGlowTop != null) {
+ mEdgeGlowTop.finish();
+ mEdgeGlowBottom.finish();
+ }
+ }
+
/**
* Sets the recycler listener to be notified whenever a View is set aside in
* the recycler for later reuse. This listener can be used to free resources
diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java
index 06334a0..93222e1 100644
--- a/core/java/android/widget/EdgeGlow.java
+++ b/core/java/android/widget/EdgeGlow.java
@@ -18,7 +18,6 @@
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
-import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
@@ -30,8 +29,6 @@
public class EdgeGlow {
private static final String TAG = "EdgeGlow";
- private static final boolean DEBUG = false;
-
// Time it will take the effect to fully recede in ms
private static final int RECEDE_TIME = 1000;
@@ -46,7 +43,10 @@
private static final float HELD_GLOW_ALPHA = 0.5f;
private static final float HELD_GLOW_SCALE_Y = 0.5f;
+ private static final float MAX_GLOW_HEIGHT = 0.33f;
+
private static final float PULL_GLOW_BEGIN = 0.5f;
+ private static final float PULL_EDGE_BEGIN = 0.6f;
// Minimum velocity that will be absorbed
private static final int MIN_VELOCITY = 750;
@@ -103,6 +103,10 @@
return mState == STATE_IDLE;
}
+ public void finish() {
+ mState = STATE_IDLE;
+ }
+
/**
* Call when the object is pulled by the user.
* @param deltaDistance Change in distance since the last call
@@ -123,7 +127,7 @@
mPullDistance += deltaDistance;
float distance = Math.abs(mPullDistance);
- mEdgeAlpha = mEdgeAlphaStart = Math.max(HELD_EDGE_ALPHA, Math.min(distance, 1.f));
+ mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, 1.f));
mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f));
mGlowAlpha = mGlowAlphaStart = Math.max(0.5f,
@@ -142,8 +146,6 @@
mEdgeScaleYFinish = mEdgeScaleY;
mGlowAlphaFinish = mGlowAlpha;
mGlowScaleYFinish = mGlowScaleY;
-
- if (DEBUG) Log.d(TAG, "onPull(" + distance + ", " + deltaDistance + ")");
}
/**
@@ -155,7 +157,6 @@
if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
return;
}
- if (DEBUG) Log.d(TAG, "onRelease");
mState = STATE_RECEDE;
mEdgeAlphaStart = mEdgeAlpha;
@@ -178,7 +179,6 @@
*/
public void onAbsorb(int velocity) {
mState = STATE_ABSORB;
- if (DEBUG) Log.d(TAG, "onAbsorb uncooked velocity: " + velocity);
velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
mStartTime = AnimationUtils.currentAnimationTimeMillis();
@@ -193,8 +193,6 @@
mEdgeScaleYFinish = 1.f;
mGlowAlphaFinish = 1.f;
mGlowScaleYFinish = Math.min(velocity * 0.001f, 1);
-
- if (DEBUG) Log.d(TAG, "onAbsorb(" + velocity + "): duration " + mDuration);
}
/**
@@ -212,8 +210,11 @@
final int edgeHeight = mEdge.getIntrinsicHeight();
final int glowHeight = mGlow.getIntrinsicHeight();
+ final float distScale = (float) mHeight / mWidth;
+
mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
- mGlow.setBounds(0, 0, mWidth, (int) (glowHeight * mGlowScaleY * 0.5f));
+ mGlow.setBounds(0, 0, mWidth, (int) Math.min(glowHeight * mGlowScaleY * distScale * 0.6f,
+ mHeight * MAX_GLOW_HEIGHT));
mGlow.draw(canvas);
mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
@@ -222,8 +223,6 @@
mWidth,
(int) (edgeHeight * mEdgeScaleY));
mEdge.draw(canvas);
- if (DEBUG) Log.d(TAG, "draw() glow(" + mGlowAlpha + ", " + mGlowScaleY + ") edge(" + mEdgeAlpha +
- ", " + mEdgeScaleY + ")");
return mState != STATE_IDLE;
}
@@ -255,7 +254,6 @@
mEdgeScaleYFinish = 0.1f;
mGlowAlphaFinish = 0.f;
mGlowScaleYFinish = mGlowScaleY;
- if (DEBUG) Log.d(TAG, "STATE_ABSORB => STATE_RECEDE");
break;
case STATE_PULL:
mState = STATE_PULL_DECAY;
@@ -271,14 +269,12 @@
mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y);
mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA);
mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y);
- if (DEBUG) Log.d(TAG, "STATE_PULL => STATE_PULL_DECAY");
break;
case STATE_PULL_DECAY:
// Do nothing; wait for release
break;
case STATE_RECEDE:
mState = STATE_IDLE;
- if (DEBUG) Log.d(TAG, "STATE_RECEDE => STATE_IDLE");
break;
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 3493f49..129ad8a 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1374,7 +1374,7 @@
if (mode != OVERSCROLL_NEVER) {
if (mEdgeGlowLeft == null) {
final Resources res = getContext().getResources();
- final Drawable edge = res.getDrawable(R.drawable.edge_light);
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
mEdgeGlowLeft = new EdgeGlow(edge, glow);
mEdgeGlowRight = new EdgeGlow(edge, glow);
@@ -1396,7 +1396,7 @@
final int height = getHeight();
canvas.rotate(270);
- canvas.translate(-height * 1.5f, scrollX);
+ canvas.translate(-height * 1.5f, Math.min(0, scrollX));
mEdgeGlowLeft.setSize(getHeight() * 2, getWidth());
if (mEdgeGlowLeft.draw(canvas)) {
invalidate();
@@ -1409,7 +1409,7 @@
final int height = getHeight();
canvas.rotate(90);
- canvas.translate(-height / 2, -scrollX - width);
+ canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width));
mEdgeGlowRight.setSize(height * 2, width);
if (mEdgeGlowRight.draw(canvas)) {
invalidate();
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 9d971f6..7b5e412 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1374,7 +1374,7 @@
if (mode != OVERSCROLL_NEVER) {
if (mEdgeGlowTop == null) {
final Resources res = getContext().getResources();
- final Drawable edge = res.getDrawable(R.drawable.edge_light);
+ final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
mEdgeGlowTop = new EdgeGlow(edge, glow);
mEdgeGlowBottom = new EdgeGlow(edge, glow);
@@ -1395,7 +1395,7 @@
final int restoreCount = canvas.save();
final int width = getWidth();
- canvas.translate(-width / 2, scrollY);
+ canvas.translate(-width / 2, Math.min(0, scrollY));
mEdgeGlowTop.setSize(width * 2, getHeight());
if (mEdgeGlowTop.draw(canvas)) {
invalidate();
@@ -1407,7 +1407,7 @@
final int width = getWidth();
final int height = getHeight();
- canvas.translate(-width / 2, scrollY + height);
+ canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height);
canvas.rotate(180, width, 0);
mEdgeGlowBottom.setSize(width * 2, height);
if (mEdgeGlowBottom.draw(canvas)) {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 62a4495..2751a82 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -308,6 +308,8 @@
jclass clazz;
jmethodID methodId;
+ LOGD("Calling main entry %s", className);
+
env = getJNIEnv();
if (env == NULL)
return UNKNOWN_ERROR;
@@ -914,7 +916,8 @@
*/
void AndroidRuntime::start(const char* className, const bool startSystemServer)
{
- LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");
+ LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
+ className != NULL ? className : "(unknown)");
char* slashClassName = NULL;
char* cp;
@@ -1029,7 +1032,7 @@
void AndroidRuntime::onExit(int code)
{
- LOGI("AndroidRuntime onExit calling exit(%d)", code);
+ LOGV("AndroidRuntime onExit calling exit(%d)", code);
exit(code);
}
@@ -1334,7 +1337,7 @@
*/
androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);
- LOGD("--- registering native functions ---\n");
+ LOGV("--- registering native functions ---\n");
/*
* Every "register" function calls one or more things that return
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 19b30cc..0323b70 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -191,20 +191,6 @@
android:label="@string/permlab_writeContacts"
android:description="@string/permdesc_writeContacts" />
- <!-- Allows an application to read the owner's data. -->
- <permission android:name="android.permission.READ_OWNER_DATA"
- android:permissionGroup="android.permission-group.PERSONAL_INFO"
- android:protectionLevel="dangerous"
- android:label="@string/permlab_readOwnerData"
- android:description="@string/permdesc_readOwnerData" />
-
- <!-- Allows an application to write (but not read) the owner's data. -->
- <permission android:name="android.permission.WRITE_OWNER_DATA"
- android:permissionGroup="android.permission-group.PERSONAL_INFO"
- android:protectionLevel="dangerous"
- android:label="@string/permlab_writeOwnerData"
- android:description="@string/permdesc_writeOwnerData" />
-
<!-- Allows an application to read the user's calendar data. -->
<permission android:name="android.permission.READ_CALENDAR"
android:permissionGroup="android.permission-group.PERSONAL_INFO"
@@ -625,7 +611,10 @@
android:label="@string/permlab_setAnimationScale"
android:description="@string/permdesc_setAnimationScale" />
- <!-- Allow an application to make its activities persistent. -->
+ <!-- @deprecated This functionality will be removed in the future; please do
+ not use.
+
+ Allow an application to make its activities persistent. -->
<permission android:name="android.permission.PERSISTENT_ACTIVITY"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="dangerous"
@@ -767,7 +756,7 @@
<!-- Allows applications to disable the keyguard -->
<permission android:name="android.permission.DISABLE_KEYGUARD"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
- android:protectionLevel="normal"
+ android:protectionLevel="dangerous"
android:description="@string/permdesc_disableKeyguard"
android:label="@string/permlab_disableKeyguard" />
diff --git a/core/res/res/drawable/edge_light.png b/core/res/res/drawable/edge_light.png
deleted file mode 100644
index b026880..0000000
--- a/core/res/res/drawable/edge_light.png
+++ /dev/null
Binary files differ
diff --git a/core/res/res/drawable/overscroll_edge.png b/core/res/res/drawable/overscroll_edge.png
new file mode 100644
index 0000000..250c827
--- /dev/null
+++ b/core/res/res/drawable/overscroll_edge.png
Binary files differ
diff --git a/core/res/res/drawable/overscroll_glow.png b/core/res/res/drawable/overscroll_glow.png
index 7f1831e..69b456d 100644
--- a/core/res/res/drawable/overscroll_glow.png
+++ b/core/res/res/drawable/overscroll_glow.png
Binary files differ
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 03b721c..29ac2ea 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -784,20 +784,6 @@
applications can use this to erase or modify your contact data.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_writeOwnerData">write owner data</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_writeOwnerData">Allows an application to modify the
- phone owner data stored on your phone. Malicious
- applications can use this to erase or modify owner data.</string>
-
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permlab_readOwnerData">read owner data</string>
- <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permdesc_readOwnerData">Allows an application read the
- phone owner data stored on your phone. Malicious
- applications can use this to read phone owner data.</string>
-
- <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCalendar">read calendar events</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_readCalendar">Allows an application to read all
diff --git a/core/tests/coretests/res/raw/v21_im.vcf b/core/tests/coretests/res/raw/v21_im.vcf
new file mode 100644
index 0000000..cc1aabb
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_im.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:2.1
+X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;Nick;1;;;;;;;;;;;;;
+X-GOOGLE-TALK:hhh@gmail.com
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf b/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf
new file mode 100644
index 0000000..9c81fd5
--- /dev/null
+++ b/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf
@@ -0,0 +1,7 @@
+BEGIN:VCARD
+VERSION:2.1
+N:;Omega;;;
+EMAIL;INTERNET:"Omega"
+ <omega@example.com>
+FN:Omega
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_comma_separated.vcf b/core/tests/coretests/res/raw/v30_comma_separated.vcf
index 98a7f20..f1baf88 100644
--- a/core/tests/coretests/res/raw/v30_comma_separated.vcf
+++ b/core/tests/coretests/res/raw/v30_comma_separated.vcf
@@ -1,5 +1,5 @@
BEGIN:VCARD
VERSION:3.0
-N:F;G;M;;
-TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+N;TYPE=PREF,HOME:F;G;M;;
+TEL;TYPE="COMMA,SEPARATED:INSIDE.DQUOTE",PREF:1
END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_pager.vcf b/core/tests/coretests/res/raw/v30_pager.vcf
new file mode 100644
index 0000000..98a7f20
--- /dev/null
+++ b/core/tests/coretests/res/raw/v30_pager.vcf
@@ -0,0 +1,5 @@
+BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v40_sort_as.vcf b/core/tests/coretests/res/raw/v40_sort_as.vcf
new file mode 100644
index 0000000..6f6bc3b
--- /dev/null
+++ b/core/tests/coretests/res/raw/v40_sort_as.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:4.0
+FN:安藤 ロイド
+N;SORT-AS="あんどう;ろいど":安藤;ロイド;;;
+ORG;TYPE=WORK;SORT-AS="ぐーぐる;けんさくぶもん":グーグル;検索部門
+END:VCARD
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java
new file mode 100644
index 0000000..33e9dd7
--- /dev/null
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothRebootStressTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.bluetooth;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+/**
+ * Instrumentation test case for stress test involving rebooting the device.
+ * <p>
+ * This test case tests that bluetooth is enabled after a device reboot. Because
+ * the device will reboot, the instrumentation must be driven by a script on the
+ * host side.
+ */
+public class BluetoothRebootStressTest extends InstrumentationTestCase {
+ private static final String TAG = "BluetoothRebootStressTest";
+ private static final String OUTPUT_FILE = "BluetoothRebootStressTestOutput.txt";
+
+ private BluetoothTestUtils mTestUtils;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ Context context = getInstrumentation().getTargetContext();
+ mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ mTestUtils.close();
+ }
+
+ /**
+ * Test method used to start the test by turning bluetooth on.
+ */
+ public void testStart() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ mTestUtils.enable(adapter);
+ }
+
+ /**
+ * Test method used in the middle iterations of the test to check if
+ * bluetooth is on. Does not toggle bluetooth after the check. Assumes that
+ * bluetooth has been turned on by {@code #testStart()}
+ */
+ public void testMiddleNoToggle() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ assertTrue(adapter.isEnabled());
+ }
+
+ /**
+ * Test method used in the middle iterations of the test to check if
+ * bluetooth is on. Toggles bluetooth after the check. Assumes that
+ * bluetooth has been turned on by {@code #testStart()}
+ */
+ public void testMiddleToggle() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ assertTrue(adapter.isEnabled());
+
+ mTestUtils.disable(adapter);
+ mTestUtils.enable(adapter);
+ }
+
+ /**
+ * Test method used in the stop the test by turning bluetooth off. Assumes
+ * that bluetooth has been turned on by {@code #testStart()}
+ */
+ public void testStop() {
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+ assertTrue(adapter.isEnabled());
+
+ mTestUtils.disable(adapter);
+ }
+}
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
index ca6ece0..149685c 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java
@@ -16,329 +16,28 @@
package android.bluetooth;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.Set;
-
-import android.app.Instrumentation;
-import android.bluetooth.BluetoothHeadset.ServiceListener;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Environment;
import android.test.InstrumentationTestCase;
-import android.util.Log;
public class BluetoothStressTest extends InstrumentationTestCase {
private static final String TAG = "BluetoothStressTest";
private static final String OUTPUT_FILE = "BluetoothStressTestOutput.txt";
- /**
- * Timeout for {@link BluetoothAdapter#disable()} in ms.
- */
- private static final int DISABLE_TIMEOUT = 5000;
-
- /**
- * Timeout for {@link BluetoothAdapter#enable()} in ms.
- */
- private static final int ENABLE_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms.
- */
- private static final int SET_SCAN_MODE_TIMEOUT = 5000;
-
- /**
- * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms.
- */
- private static final int START_DISCOVERY_TIMEOUT = 5000;
-
- /**
- * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms.
- */
- private static final int CANCEL_DISCOVERY_TIMEOUT = 5000;
-
- /**
- * Timeout for {@link BluetoothDevice#createBond()} in ms.
- */
- private static final int PAIR_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothDevice#removeBond()} in ms.
- */
- private static final int UNPAIR_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothA2dp#connectSink(BluetoothDevice)} in ms.
- */
- private static final int CONNECT_A2DP_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothA2dp#disconnectSink(BluetoothDevice)} in ms.
- */
- private static final int DISCONNECT_A2DP_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothHeadset#connectHeadset(BluetoothDevice)} in ms.
- */
- private static final int CONNECT_HEADSET_TIMEOUT = 20000;
-
- /**
- * Timeout for {@link BluetoothHeadset#disconnectHeadset(BluetoothDevice)} in ms.
- */
- private static final int DISCONNECT_HEADSET_TIMEOUT = 20000;
-
- private static final int DISCOVERY_STARTED_FLAG = 1;
- private static final int DISCOVERY_FINISHED_FLAG = 1 << 1;
- private static final int SCAN_MODE_NONE_FLAG = 1 << 2;
- private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3;
- private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4;
- private static final int STATE_OFF_FLAG = 1 << 5;
- private static final int STATE_TURNING_ON_FLAG = 1 << 6;
- private static final int STATE_ON_FLAG = 1 << 7;
- private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
- private static final int PAIR_STATE_FLAG = 1 << 9;
- private static final int PROFILE_A2DP_FLAG = 1 << 10;
- private static final int PROFILE_HEADSET_FLAG = 1 << 11;
-
- private static final int PAIR_STATE_BONDED = 1;
- private static final int PAIR_STATE_BONDING = 1 << 1;
- private static final int PAIR_STATE_NONE = 1 << 2;
-
- private static final int A2DP_STATE_DISCONNECTED = 1;
- private static final int A2DP_STATE_CONNECTING = 1 << 1;
- private static final int A2DP_STATE_CONNECTED = 1 << 2;
- private static final int A2DP_STATE_DISCONNECTING = 1 << 3;
- private static final int A2DP_STATE_PLAYING = 1 << 4;
-
- private static final int HEADSET_STATE_DISCONNECTED = 1;
- private static final int HEADSET_STATE_CONNECTING = 1 << 1;
- private static final int HEADSET_STATE_CONNECTED = 1 << 2;
-
- /**
- * Time between polls in ms.
- */
- private static final int POLL_TIME = 100;
-
- private Context mContext;
-
- private Instrumentation mInstrumentation;
-
- private BufferedWriter mOutputWriter;
-
- private BluetoothA2dp mA2dp;
-
- private BluetoothHeadset mHeadset;
-
- private class HeadsetServiceListener implements ServiceListener {
- private boolean mConnected = false;
-
- @Override
- public void onServiceConnected() {
- synchronized (this) {
- mConnected = true;
- }
- }
-
- @Override
- public void onServiceDisconnected() {
- synchronized (this) {
- mConnected = false;
- }
- }
-
- public boolean isConnected() {
- synchronized (this) {
- return mConnected;
- }
- }
- }
-
- private HeadsetServiceListener mHeadsetServiceListener = new HeadsetServiceListener();
-
- private class BluetoothReceiver extends BroadcastReceiver {
- private int mFiredFlags = 0;
- private int mPairFiredFlags = 0;
- private int mA2dpFiredFlags = 0;
- private int mHeadsetFiredFlags = 0;
-
- @Override
- public void onReceive(Context context, Intent intent) {
- synchronized (this) {
- if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
- mFiredFlags |= DISCOVERY_STARTED_FLAG;
- } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
- mFiredFlags |= DISCOVERY_FINISHED_FLAG;
- } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
- int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
- BluetoothAdapter.ERROR);
- assertNotSame(mode, BluetoothAdapter.ERROR);
- switch (mode) {
- case BluetoothAdapter.SCAN_MODE_NONE:
- mFiredFlags |= SCAN_MODE_NONE_FLAG;
- break;
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
- mFiredFlags |= SCAN_MODE_CONNECTABLE_FLAG;
- break;
- case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
- mFiredFlags |= SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
- break;
- }
- } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
- BluetoothAdapter.ERROR);
- assertNotSame(state, BluetoothAdapter.ERROR);
- switch (state) {
- case BluetoothAdapter.STATE_OFF:
- mFiredFlags |= STATE_OFF_FLAG;
- break;
- case BluetoothAdapter.STATE_TURNING_ON:
- mFiredFlags |= STATE_TURNING_ON_FLAG;
- break;
- case BluetoothAdapter.STATE_ON:
- mFiredFlags |= STATE_ON_FLAG;
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- mFiredFlags |= STATE_TURNING_OFF_FLAG;
- break;
- }
- } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
- mFiredFlags |= PAIR_STATE_FLAG;
- int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
- assertNotSame(state, -1);
- switch (state) {
- case BluetoothDevice.BOND_BONDED:
- mPairFiredFlags |= PAIR_STATE_BONDED;
- break;
- case BluetoothDevice.BOND_BONDING:
- mPairFiredFlags |= PAIR_STATE_BONDING;
- break;
- case BluetoothDevice.BOND_NONE:
- mPairFiredFlags |= PAIR_STATE_NONE;
- break;
- }
- } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) {
- mFiredFlags |= PROFILE_A2DP_FLAG;
- int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, -1);
- assertNotSame(state, -1);
- switch (state) {
- case BluetoothA2dp.STATE_DISCONNECTED:
- mA2dpFiredFlags |= A2DP_STATE_DISCONNECTED;
- break;
- case BluetoothA2dp.STATE_CONNECTING:
- mA2dpFiredFlags |= A2DP_STATE_CONNECTING;
- break;
- case BluetoothA2dp.STATE_CONNECTED:
- mA2dpFiredFlags |= A2DP_STATE_CONNECTED;
- break;
- case BluetoothA2dp.STATE_DISCONNECTING:
- mA2dpFiredFlags |= A2DP_STATE_DISCONNECTING;
- break;
- case BluetoothA2dp.STATE_PLAYING:
- mA2dpFiredFlags |= A2DP_STATE_PLAYING;
- break;
- }
- } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) {
- mFiredFlags |= PROFILE_HEADSET_FLAG;
- int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
- BluetoothHeadset.STATE_ERROR);
- assertNotSame(state, BluetoothHeadset.STATE_ERROR);
- switch (state) {
- case BluetoothHeadset.STATE_DISCONNECTED:
- mHeadsetFiredFlags |= HEADSET_STATE_DISCONNECTED;
- break;
- case BluetoothHeadset.STATE_CONNECTING:
- mHeadsetFiredFlags |= HEADSET_STATE_CONNECTING;
- break;
- case BluetoothHeadset.STATE_CONNECTED:
- mHeadsetFiredFlags |= HEADSET_STATE_CONNECTED;
- break;
- }
- }
- }
- }
-
- public int getFiredFlags() {
- synchronized (this) {
- return mFiredFlags;
- }
- }
-
- public int getPairFiredFlags() {
- synchronized (this) {
- return mPairFiredFlags;
- }
- }
-
- public int getA2dpFiredFlags() {
- synchronized (this) {
- return mA2dpFiredFlags;
- }
- }
-
- public int getHeadsetFiredFlags() {
- synchronized (this) {
- return mHeadsetFiredFlags;
- }
- }
-
- public void resetFiredFlags() {
- synchronized (this) {
- mFiredFlags = 0;
- mPairFiredFlags = 0;
- mA2dpFiredFlags = 0;
- mHeadsetFiredFlags = 0;
- }
- }
- }
-
- private BluetoothReceiver mReceiver = new BluetoothReceiver();
+ private BluetoothTestUtils mTestUtils;
@Override
protected void setUp() throws Exception {
super.setUp();
- mInstrumentation = getInstrumentation();
- mContext = mInstrumentation.getTargetContext();
-
- try {
- mOutputWriter = new BufferedWriter(new FileWriter(new File(
- Environment.getExternalStorageDirectory(), OUTPUT_FILE), true));
- } catch (IOException e) {
- Log.w(TAG, "Test output file could not be opened", e);
- mOutputWriter = null;
- }
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
- filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
- filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
- filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
- filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
- filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
- filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED);
- mContext.registerReceiver(mReceiver, filter);
-
- mA2dp = new BluetoothA2dp(mContext);
- mHeadset = new BluetoothHeadset(mContext, mHeadsetServiceListener);
+ Context context = getInstrumentation().getTargetContext();
+ mTestUtils = new BluetoothTestUtils(context, TAG, OUTPUT_FILE);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
- mContext.unregisterReceiver(mReceiver);
-
- if (mOutputWriter != null) {
- try {
- mOutputWriter.close();
- } catch (IOException e) {
- Log.w(TAG, "Test output file could not be closed", e);
- }
- }
+ mTestUtils.close();
}
public void testEnable() {
@@ -346,48 +45,48 @@
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
for (int i = 0; i < iterations; i++) {
- writeOutput("enable iteration " + (i + 1) + " of " + iterations);
- enable(adapter);
- disable(adapter);
+ mTestUtils.writeOutput("enable iteration " + (i + 1) + " of " + iterations);
+ mTestUtils.enable(adapter);
+ mTestUtils.disable(adapter);
}
}
public void testDiscoverable() {
int iterations = BluetoothTestRunner.sDiscoverableIterations;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- enable(adapter);
+ mTestUtils.enable(adapter);
for (int i = 0; i < iterations; i++) {
- writeOutput("discoverable iteration " + (i + 1) + " of " + iterations);
- discoverable(adapter);
- undiscoverable(adapter);
+ mTestUtils.writeOutput("discoverable iteration " + (i + 1) + " of " + iterations);
+ mTestUtils.discoverable(adapter);
+ mTestUtils.undiscoverable(adapter);
}
- disable(adapter);
+ mTestUtils.disable(adapter);
}
public void testScan() {
int iterations = BluetoothTestRunner.sScanIterations;
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- enable(adapter);
+ mTestUtils.enable(adapter);
for (int i = 0; i < iterations; i++) {
- writeOutput("scan iteration " + (i + 1) + " of " + iterations);
- startScan(adapter);
- stopScan(adapter);
+ mTestUtils.writeOutput("scan iteration " + (i + 1) + " of " + iterations);
+ mTestUtils.startScan(adapter);
+ mTestUtils.stopScan(adapter);
}
- disable(adapter);
+ mTestUtils.disable(adapter);
}
public void testPair() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress);
- enable(adapter);
- pair(adapter, device);
- unpair(adapter, device);
- disable(adapter);
+ mTestUtils.enable(adapter);
+ mTestUtils.pair(adapter, device);
+ mTestUtils.unpair(adapter, device);
+ mTestUtils.disable(adapter);
}
public void testConnectA2dp() {
@@ -395,17 +94,17 @@
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sA2dpAddress);
- enable(adapter);
- pair(adapter, device);
+ mTestUtils.enable(adapter);
+ mTestUtils.pair(adapter, device);
for (int i = 0; i < iterations; i++) {
- writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations);
- connectA2dp(adapter, device);
- disconnectA2dp(adapter, device);
+ mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations);
+ mTestUtils.connectA2dp(adapter, device);
+ mTestUtils.disconnectA2dp(adapter, device);
}
// TODO: Unpair from device if device can accept pairing after unpairing
- disable(adapter);
+ mTestUtils.disable(adapter);
}
public void testConnectHeadset() {
@@ -413,618 +112,16 @@
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress);
- enable(adapter);
- pair(adapter, device);
+ mTestUtils.enable(adapter);
+ mTestUtils.pair(adapter, device);
for (int i = 0; i < iterations; i++) {
- writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations);
- connectHeadset(adapter, device);
- disconnectHeadset(adapter, device);
+ mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations);
+ mTestUtils.connectHeadset(adapter, device);
+ mTestUtils.disconnectHeadset(adapter, device);
}
- disable(adapter);
- }
-
- private void disable(BluetoothAdapter adapter) {
- int mask = STATE_TURNING_OFF_FLAG | STATE_OFF_FLAG | SCAN_MODE_NONE_FLAG;
- mReceiver.resetFiredFlags();
-
- int state = adapter.getState();
- switch (state) {
- case BluetoothAdapter.STATE_OFF:
- assertFalse(adapter.isEnabled());
- return;
- case BluetoothAdapter.STATE_ON:
- assertTrue(adapter.isEnabled());
- assertTrue(adapter.disable());
- break;
- case BluetoothAdapter.STATE_TURNING_ON:
- assertFalse(adapter.isEnabled());
- assertTrue(adapter.disable());
- break;
- case BluetoothAdapter.STATE_TURNING_OFF:
- assertFalse(adapter.isEnabled());
- mask = 0; // Don't check for received intents since we might have missed them.
- break;
- default:
- fail("disable() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) {
- state = adapter.getState();
- if (state == BluetoothAdapter.STATE_OFF) {
- assertFalse(adapter.isEnabled());
- if ((mReceiver.getFiredFlags() & mask) == mask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("disable() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- } else {
- assertFalse(adapter.isEnabled());
- assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
- state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
- }
-
- private void enable(BluetoothAdapter adapter) {
- int mask = STATE_TURNING_ON_FLAG | STATE_ON_FLAG | SCAN_MODE_CONNECTABLE_FLAG;
- mReceiver.resetFiredFlags();
-
- int state = adapter.getState();
- switch (state) {
- case BluetoothAdapter.STATE_ON:
- assertTrue(adapter.isEnabled());
- return;
- case BluetoothAdapter.STATE_OFF:
- case BluetoothAdapter.STATE_TURNING_OFF:
- assertFalse(adapter.isEnabled());
- assertTrue(adapter.enable());
- break;
- case BluetoothAdapter.STATE_TURNING_ON:
- assertFalse(adapter.isEnabled());
- mask = 0; // Don't check for received intents since we might have missed them.
- break;
- default:
- fail("enable() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) {
- state = adapter.getState();
- if (state == BluetoothAdapter.STATE_ON) {
- assertTrue(adapter.isEnabled());
- if ((mReceiver.getFiredFlags() & mask) == mask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("enable() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- } else {
- assertFalse(adapter.isEnabled());
- assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
- state, BluetoothAdapter.STATE_ON, firedFlags, mask));
- }
-
- private void discoverable(BluetoothAdapter adapter) {
- int mask = SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("discoverable() bluetooth not enabled");
- }
-
- int scanMode = adapter.getScanMode();
- if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
- return;
- }
-
- assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
- assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
- scanMode = adapter.getScanMode();
- if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
- if ((mReceiver.getFiredFlags() & mask) == mask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("discoverable() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- } else {
- assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
- + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
- firedFlags, mask));
- }
-
- private void undiscoverable(BluetoothAdapter adapter) {
- int mask = SCAN_MODE_CONNECTABLE_FLAG;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("undiscoverable() bluetooth not enabled");
- }
-
- int scanMode = adapter.getScanMode();
- if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
- return;
- }
-
- assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
- assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
- scanMode = adapter.getScanMode();
- if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
- if ((mReceiver.getFiredFlags() & mask) == mask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("undiscoverable() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- } else {
- assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
- + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
- mask));
- }
-
- private void startScan(BluetoothAdapter adapter) {
- int mask = DISCOVERY_STARTED_FLAG;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("startScan() bluetooth not enabled");
- }
-
- if (adapter.isDiscovering()) {
- return;
- }
-
- assertTrue(adapter.startDiscovery());
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < START_DISCOVERY_TIMEOUT) {
- if (adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("startScan() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
- adapter.isDiscovering(), firedFlags, mask));
- }
-
- private void stopScan(BluetoothAdapter adapter) {
- int mask = DISCOVERY_FINISHED_FLAG;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("stopScan() bluetooth not enabled");
- }
-
- if (!adapter.isDiscovering()) {
- return;
- }
-
- // TODO: put assertTrue() around cancelDiscovery() once it starts
- // returning true.
- adapter.cancelDiscovery();
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < CANCEL_DISCOVERY_TIMEOUT) {
- if (!adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("stopScan() completed in %d ms",
- (System.currentTimeMillis() - s)));
- return;
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
- adapter.isDiscovering(), firedFlags, mask));
-
- }
-
- private void pair(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PAIR_STATE_FLAG;
- int pairMask = PAIR_STATE_BONDING | PAIR_STATE_BONDED;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("pair() bluetooth not enabled");
- }
-
- int state = device.getBondState();
- switch (state) {
- case BluetoothDevice.BOND_BONDED:
- assertTrue(adapter.getBondedDevices().contains(device));
- return;
- case BluetoothDevice.BOND_BONDING:
- // Don't check for received intents since we might have missed them.
- mask = pairMask = 0;
- break;
- case BluetoothDevice.BOND_NONE:
- assertFalse(adapter.getBondedDevices().contains(device));
- assertTrue(device.createBond());
- break;
- default:
- fail("pair() invalide state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < PAIR_TIMEOUT) {
- state = device.getBondState();
- if (state == BluetoothDevice.BOND_BONDED) {
- assertTrue(adapter.getBondedDevices().contains(device));
- if ((mReceiver.getFiredFlags() & mask) == mask
- && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
- writeOutput(String.format("pair() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int pairFiredFlags = mReceiver.getPairFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("pair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
- + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
- mask, pairFiredFlags, pairMask));
- }
-
- private void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PAIR_STATE_FLAG;
- int pairMask = PAIR_STATE_NONE;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("unpair() bluetooth not enabled");
- }
-
- int state = device.getBondState();
- switch (state) {
- case BluetoothDevice.BOND_BONDED:
- assertTrue(adapter.getBondedDevices().contains(device));
- assertTrue(device.removeBond());
- break;
- case BluetoothDevice.BOND_BONDING:
- assertTrue(device.removeBond());
- break;
- case BluetoothDevice.BOND_NONE:
- assertFalse(adapter.getBondedDevices().contains(device));
- return;
- default:
- fail("unpair() invalid state: state=" + state);
- }
-
- assertTrue(device.removeBond());
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) {
- if (device.getBondState() == BluetoothDevice.BOND_NONE) {
- assertFalse(adapter.getBondedDevices().contains(device));
- if ((mReceiver.getFiredFlags() & mask) == mask
- && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
- writeOutput(String.format("unpair() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int pairFiredFlags = mReceiver.getPairFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("unpair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
- + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
- mask, pairFiredFlags, pairMask));
- }
-
- private void connectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PROFILE_A2DP_FLAG;
- int a2dpMask1 = A2DP_STATE_CONNECTING | A2DP_STATE_CONNECTED | A2DP_STATE_PLAYING;
- int a2dpMask2 = a2dpMask1 ^ A2DP_STATE_CONNECTED;
- int a2dpMask3 = a2dpMask1 ^ A2DP_STATE_PLAYING;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("connectA2dp() bluetooth not enabled");
- }
-
- if (!adapter.getBondedDevices().contains(device)) {
- fail("connectA2dp() device not paired: device=" + device);
- }
-
- int state = mA2dp.getSinkState(device);
- switch (state) {
- case BluetoothA2dp.STATE_CONNECTED:
- case BluetoothA2dp.STATE_PLAYING:
- assertTrue(mA2dp.isSinkConnected(device));
- return;
- case BluetoothA2dp.STATE_DISCONNECTING:
- case BluetoothA2dp.STATE_DISCONNECTED:
- assertFalse(mA2dp.isSinkConnected(device));
- assertTrue(mA2dp.connectSink(device));
- break;
- case BluetoothA2dp.STATE_CONNECTING:
- assertFalse(mA2dp.isSinkConnected(device));
- // Don't check for received intents since we might have missed them.
- mask = a2dpMask1 = a2dpMask2 = a2dpMask3 = 0;
- break;
- default:
- fail("connectA2dp() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < CONNECT_A2DP_TIMEOUT) {
- state = mA2dp.getSinkState(device);
- if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
- assertTrue(mA2dp.isSinkConnected(device));
- // Check whether STATE_CONNECTING and (STATE_CONNECTED or STATE_PLAYING) intents
- // have fired if we are checking if intents should be fired.
- int firedFlags = mReceiver.getFiredFlags();
- int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
- if ((mReceiver.getFiredFlags() & mask) == mask
- && ((a2dpFiredFlags & a2dpMask1) == a2dpMask1
- || (a2dpFiredFlags & a2dpMask2) == a2dpMask2
- || (a2dpFiredFlags & a2dpMask3) == a2dpMask3)) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("connectA2dp() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("connectA2dp() timeout: state=%d (expected %d or %d), "
- + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x or 0x%x or 0x%x)",
- state, BluetoothHeadset.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING, firedFlags,
- mask, a2dpFiredFlags, a2dpMask1, a2dpMask2, a2dpMask3));
- }
-
- private void disconnectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PROFILE_A2DP_FLAG;
- int a2dpMask = A2DP_STATE_DISCONNECTING | A2DP_STATE_DISCONNECTED;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("disconnectA2dp() bluetooth not enabled");
- }
-
- if (!adapter.getBondedDevices().contains(device)) {
- fail("disconnectA2dp() device not paired: device=" + device);
- }
-
- int state = mA2dp.getSinkState(device);
- switch (state) {
- case BluetoothA2dp.STATE_DISCONNECTED:
- assertFalse(mA2dp.isSinkConnected(device));
- return;
- case BluetoothA2dp.STATE_CONNECTED:
- case BluetoothA2dp.STATE_PLAYING:
- assertTrue(mA2dp.isSinkConnected(device));
- assertTrue(mA2dp.disconnectSink(device));
- break;
- case BluetoothA2dp.STATE_CONNECTING:
- assertFalse(mA2dp.isSinkConnected(device));
- assertTrue(mA2dp.disconnectSink(device));
- break;
- case BluetoothA2dp.STATE_DISCONNECTING:
- assertFalse(mA2dp.isSinkConnected(device));
- // Don't check for received intents since we might have missed them.
- mask = a2dpMask = 0;
- break;
- default:
- fail("disconnectA2dp() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < DISCONNECT_A2DP_TIMEOUT) {
- state = mA2dp.getSinkState(device);
- if (state == BluetoothA2dp.STATE_DISCONNECTED) {
- assertFalse(mA2dp.isSinkConnected(device));
- if ((mReceiver.getFiredFlags() & mask) == mask
- && (mReceiver.getA2dpFiredFlags() & a2dpMask) == a2dpMask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("disconnectA2dp() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("disconnectA2dp() timeout: state=%d (expected %d), "
- + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x)", state,
- BluetoothA2dp.STATE_DISCONNECTED, firedFlags, mask, a2dpFiredFlags, a2dpMask));
- }
-
- private void connectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PROFILE_HEADSET_FLAG;
- int headsetMask = HEADSET_STATE_CONNECTING | HEADSET_STATE_CONNECTED;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("connectHeadset() bluetooth not enabled");
- }
-
- if (!adapter.getBondedDevices().contains(device)) {
- fail("connectHeadset() device not paired: device=" + device);
- }
-
- while (!mHeadsetServiceListener.isConnected()) {
- sleep(POLL_TIME);
- }
-
- int state = mHeadset.getState(device);
- switch (state) {
- case BluetoothHeadset.STATE_CONNECTED:
- assertTrue(mHeadset.isConnected(device));
- return;
- case BluetoothHeadset.STATE_DISCONNECTED:
- assertFalse(mHeadset.isConnected(device));
- mHeadset.connectHeadset(device);
- break;
- case BluetoothHeadset.STATE_CONNECTING:
- assertFalse(mHeadset.isConnected(device));
- // Don't check for received intents since we might have missed them.
- mask = headsetMask = 0;
- break;
- case BluetoothHeadset.STATE_ERROR:
- fail("connectHeadset() error state");
- break;
- default:
- fail("connectHeadset() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < CONNECT_HEADSET_TIMEOUT) {
- state = mHeadset.getState(device);
- if (state == BluetoothHeadset.STATE_CONNECTED) {
- assertTrue(mHeadset.isConnected(device));
- if ((mReceiver.getFiredFlags() & mask) == mask
- && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("connectHeadset() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("connectHeadset() timeout: state=%d (expected %d), "
- + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
- BluetoothHeadset.STATE_CONNECTED, firedFlags, mask, headsetFiredFlags,
- headsetMask));
- }
-
- private void disconnectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
- int mask = PROFILE_HEADSET_FLAG;
- int headsetMask = HEADSET_STATE_DISCONNECTED;
- mReceiver.resetFiredFlags();
-
- if (!adapter.isEnabled()) {
- fail("disconnectHeadset() bluetooth not enabled");
- }
-
- if (!adapter.getBondedDevices().contains(device)) {
- fail("disconnectHeadset() device not paired: device=" + device);
- }
-
- while (!mHeadsetServiceListener.isConnected()) {
- sleep(POLL_TIME);
- }
-
- int state = mHeadset.getState(device);
- switch (state) {
- case BluetoothHeadset.STATE_CONNECTED:
- mHeadset.disconnectHeadset(device);
- break;
- case BluetoothHeadset.STATE_CONNECTING:
- mHeadset.disconnectHeadset(device);
- break;
- case BluetoothHeadset.STATE_DISCONNECTED:
- return;
- case BluetoothHeadset.STATE_ERROR:
- fail("disconnectHeadset() error state");
- break;
- default:
- fail("disconnectHeadset() invalid state: state=" + state);
- }
-
- long s = System.currentTimeMillis();
- while (System.currentTimeMillis() - s < DISCONNECT_HEADSET_TIMEOUT) {
- state = mHeadset.getState(device);
- if (state == BluetoothHeadset.STATE_DISCONNECTED) {
- assertFalse(mHeadset.isConnected(device));
- if ((mReceiver.getFiredFlags() & mask) == mask
- && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
- mReceiver.resetFiredFlags();
- writeOutput(String.format("disconnectHeadset() completed in %d ms: device=%s",
- (System.currentTimeMillis() - s), device));
- return;
- }
- }
- sleep(POLL_TIME);
- }
-
- int firedFlags = mReceiver.getFiredFlags();
- int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
- mReceiver.resetFiredFlags();
- fail(String.format("disconnectHeadset() timeout: state=%d (expected %d), "
- + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
- BluetoothHeadset.STATE_DISCONNECTED, firedFlags, mask, headsetFiredFlags,
- headsetMask));
- }
-
- private void writeOutput(String s) {
- if (mOutputWriter == null) {
- return;
- }
- try {
- Log.i(TAG, s);
- mOutputWriter.write(s + "\n");
- mOutputWriter.flush();
- } catch (IOException e) {
- Log.w(TAG, "Could not write to output file", e);
- }
- }
-
- private void sleep(long time) {
- try {
- Thread.sleep(time);
- } catch (InterruptedException e) {
- }
+ // TODO: Unpair from device if device can accept pairing after unpairing
+ mTestUtils.disable(adapter);
}
}
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
index e3d2eb6..2e6daa3 100644
--- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java
@@ -26,8 +26,8 @@
public static int sEnableIterations = 100;
public static int sDiscoverableIterations = 1000;
public static int sScanIterations = 1000;
- public static int sConnectHeadsetIterations = 1000;
- public static int sConnectA2dpIterations = 1000;
+ public static int sConnectHeadsetIterations = 100;
+ public static int sConnectA2dpIterations = 100;
public static String sHeadsetAddress = "";
public static String sA2dpAddress = "";
diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
new file mode 100644
index 0000000..e9311e0
--- /dev/null
+++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java
@@ -0,0 +1,939 @@
+/*
+ * 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.bluetooth;
+
+import android.bluetooth.BluetoothHeadset.ServiceListener;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Environment;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+public class BluetoothTestUtils extends Assert {
+
+ /**
+ * Timeout for {@link BluetoothAdapter#disable()} in ms.
+ */
+ private static final int DISABLE_TIMEOUT = 5000;
+
+ /**
+ * Timeout for {@link BluetoothAdapter#enable()} in ms.
+ */
+ private static final int ENABLE_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothAdapter#setScanMode(int)} in ms.
+ */
+ private static final int SET_SCAN_MODE_TIMEOUT = 5000;
+
+ /**
+ * Timeout for {@link BluetoothAdapter#startDiscovery()} in ms.
+ */
+ private static final int START_DISCOVERY_TIMEOUT = 5000;
+
+ /**
+ * Timeout for {@link BluetoothAdapter#cancelDiscovery()} in ms.
+ */
+ private static final int CANCEL_DISCOVERY_TIMEOUT = 5000;
+
+ /**
+ * Timeout for {@link BluetoothDevice#createBond()} in ms.
+ */
+ private static final int PAIR_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothDevice#removeBond()} in ms.
+ */
+ private static final int UNPAIR_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothA2dp#connectSink(BluetoothDevice)} in ms.
+ */
+ private static final int CONNECT_A2DP_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothA2dp#disconnectSink(BluetoothDevice)} in ms.
+ */
+ private static final int DISCONNECT_A2DP_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothHeadset#connectHeadset(BluetoothDevice)} in ms.
+ */
+ private static final int CONNECT_HEADSET_TIMEOUT = 20000;
+
+ /**
+ * Timeout for {@link BluetoothHeadset#disconnectHeadset(BluetoothDevice)} in ms.
+ */
+ private static final int DISCONNECT_HEADSET_TIMEOUT = 20000;
+
+ private static final int DISCOVERY_STARTED_FLAG = 1;
+ private static final int DISCOVERY_FINISHED_FLAG = 1 << 1;
+ private static final int SCAN_MODE_NONE_FLAG = 1 << 2;
+ private static final int SCAN_MODE_CONNECTABLE_FLAG = 1 << 3;
+ private static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG = 1 << 4;
+ private static final int STATE_OFF_FLAG = 1 << 5;
+ private static final int STATE_TURNING_ON_FLAG = 1 << 6;
+ private static final int STATE_ON_FLAG = 1 << 7;
+ private static final int STATE_TURNING_OFF_FLAG = 1 << 8;
+ private static final int PAIR_STATE_FLAG = 1 << 9;
+ private static final int PROFILE_A2DP_FLAG = 1 << 10;
+ private static final int PROFILE_HEADSET_FLAG = 1 << 11;
+
+ private static final int PAIR_STATE_BONDED = 1;
+ private static final int PAIR_STATE_BONDING = 1 << 1;
+ private static final int PAIR_STATE_NONE = 1 << 2;
+
+ private static final int A2DP_STATE_DISCONNECTED = 1;
+ private static final int A2DP_STATE_CONNECTING = 1 << 1;
+ private static final int A2DP_STATE_CONNECTED = 1 << 2;
+ private static final int A2DP_STATE_DISCONNECTING = 1 << 3;
+ private static final int A2DP_STATE_PLAYING = 1 << 4;
+
+ private static final int HEADSET_STATE_DISCONNECTED = 1;
+ private static final int HEADSET_STATE_CONNECTING = 1 << 1;
+ private static final int HEADSET_STATE_CONNECTED = 1 << 2;
+
+ /**
+ * Time between polls in ms.
+ */
+ private static final int POLL_TIME = 100;
+
+ private Context mContext;
+
+ private BufferedWriter mOutputWriter;
+
+ private BluetoothA2dp mA2dp;
+
+ private BluetoothHeadset mHeadset;
+
+ private String mOutputFile;
+ private String mTag;
+ private class HeadsetServiceListener implements ServiceListener {
+ private boolean mConnected = false;
+
+ @Override
+ public void onServiceConnected() {
+ synchronized (this) {
+ mConnected = true;
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected() {
+ synchronized (this) {
+ mConnected = false;
+ }
+ }
+
+ public boolean isConnected() {
+ synchronized (this) {
+ return mConnected;
+ }
+ }
+ }
+
+ private HeadsetServiceListener mHeadsetServiceListener = new HeadsetServiceListener();
+
+ private class BluetoothReceiver extends BroadcastReceiver {
+ private int mFiredFlags = 0;
+ private int mPairFiredFlags = 0;
+ private int mA2dpFiredFlags = 0;
+ private int mHeadsetFiredFlags = 0;
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (this) {
+ if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
+ mFiredFlags |= DISCOVERY_STARTED_FLAG;
+ } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
+ mFiredFlags |= DISCOVERY_FINISHED_FLAG;
+ } else if (BluetoothAdapter.ACTION_SCAN_MODE_CHANGED.equals(intent.getAction())) {
+ int mode = intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE,
+ BluetoothAdapter.ERROR);
+ assertNotSame(mode, BluetoothAdapter.ERROR);
+ switch (mode) {
+ case BluetoothAdapter.SCAN_MODE_NONE:
+ mFiredFlags |= SCAN_MODE_NONE_FLAG;
+ break;
+ case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
+ mFiredFlags |= SCAN_MODE_CONNECTABLE_FLAG;
+ break;
+ case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
+ mFiredFlags |= SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
+ break;
+ }
+ } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+ BluetoothAdapter.ERROR);
+ assertNotSame(state, BluetoothAdapter.ERROR);
+ switch (state) {
+ case BluetoothAdapter.STATE_OFF:
+ mFiredFlags |= STATE_OFF_FLAG;
+ break;
+ case BluetoothAdapter.STATE_TURNING_ON:
+ mFiredFlags |= STATE_TURNING_ON_FLAG;
+ break;
+ case BluetoothAdapter.STATE_ON:
+ mFiredFlags |= STATE_ON_FLAG;
+ break;
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ mFiredFlags |= STATE_TURNING_OFF_FLAG;
+ break;
+ }
+ } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) {
+ mFiredFlags |= PAIR_STATE_FLAG;
+ int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1);
+ assertNotSame(state, -1);
+ switch (state) {
+ case BluetoothDevice.BOND_BONDED:
+ mPairFiredFlags |= PAIR_STATE_BONDED;
+ break;
+ case BluetoothDevice.BOND_BONDING:
+ mPairFiredFlags |= PAIR_STATE_BONDING;
+ break;
+ case BluetoothDevice.BOND_NONE:
+ mPairFiredFlags |= PAIR_STATE_NONE;
+ break;
+ }
+ } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) {
+ mFiredFlags |= PROFILE_A2DP_FLAG;
+ int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, -1);
+ assertNotSame(state, -1);
+ switch (state) {
+ case BluetoothA2dp.STATE_DISCONNECTED:
+ mA2dpFiredFlags |= A2DP_STATE_DISCONNECTED;
+ break;
+ case BluetoothA2dp.STATE_CONNECTING:
+ mA2dpFiredFlags |= A2DP_STATE_CONNECTING;
+ break;
+ case BluetoothA2dp.STATE_CONNECTED:
+ mA2dpFiredFlags |= A2DP_STATE_CONNECTED;
+ break;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ mA2dpFiredFlags |= A2DP_STATE_DISCONNECTING;
+ break;
+ case BluetoothA2dp.STATE_PLAYING:
+ mA2dpFiredFlags |= A2DP_STATE_PLAYING;
+ break;
+ }
+ } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) {
+ mFiredFlags |= PROFILE_HEADSET_FLAG;
+ int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_ERROR);
+ assertNotSame(state, BluetoothHeadset.STATE_ERROR);
+ switch (state) {
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ mHeadsetFiredFlags |= HEADSET_STATE_DISCONNECTED;
+ break;
+ case BluetoothHeadset.STATE_CONNECTING:
+ mHeadsetFiredFlags |= HEADSET_STATE_CONNECTING;
+ break;
+ case BluetoothHeadset.STATE_CONNECTED:
+ mHeadsetFiredFlags |= HEADSET_STATE_CONNECTED;
+ break;
+ }
+ }
+ }
+ }
+
+ public int getFiredFlags() {
+ synchronized (this) {
+ return mFiredFlags;
+ }
+ }
+
+ public int getPairFiredFlags() {
+ synchronized (this) {
+ return mPairFiredFlags;
+ }
+ }
+
+ public int getA2dpFiredFlags() {
+ synchronized (this) {
+ return mA2dpFiredFlags;
+ }
+ }
+
+ public int getHeadsetFiredFlags() {
+ synchronized (this) {
+ return mHeadsetFiredFlags;
+ }
+ }
+
+ public void resetFiredFlags() {
+ synchronized (this) {
+ mFiredFlags = 0;
+ mPairFiredFlags = 0;
+ mA2dpFiredFlags = 0;
+ mHeadsetFiredFlags = 0;
+ }
+ }
+ }
+
+ private BluetoothReceiver mReceiver = new BluetoothReceiver();
+
+ public BluetoothTestUtils(Context context, String tag) {
+ this(context, tag, null);
+ }
+
+ public BluetoothTestUtils(Context context, String tag, String outputFile) {
+ mContext = context;
+ mTag = tag;
+ mOutputFile = outputFile;
+
+ if (mOutputFile == null) {
+ mOutputWriter = null;
+ } else {
+ try {
+ mOutputWriter = new BufferedWriter(new FileWriter(new File(
+ Environment.getExternalStorageDirectory(), mOutputFile), true));
+ } catch (IOException e) {
+ Log.w(mTag, "Test output file could not be opened", e);
+ mOutputWriter = null;
+ }
+ }
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
+ filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
+ filter.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
+ filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
+ mContext.registerReceiver(mReceiver, filter);
+ }
+
+ public void close() {
+ mContext.unregisterReceiver(mReceiver);
+
+ if (mOutputWriter != null) {
+ try {
+ mOutputWriter.close();
+ } catch (IOException e) {
+ Log.w(mTag, "Test output file could not be closed", e);
+ }
+ }
+ }
+
+ public void enable(BluetoothAdapter adapter) {
+ int mask = STATE_TURNING_ON_FLAG | STATE_ON_FLAG | SCAN_MODE_CONNECTABLE_FLAG;
+ mReceiver.resetFiredFlags();
+
+ int state = adapter.getState();
+ switch (state) {
+ case BluetoothAdapter.STATE_ON:
+ assertTrue(adapter.isEnabled());
+ return;
+ case BluetoothAdapter.STATE_OFF:
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ assertFalse(adapter.isEnabled());
+ assertTrue(adapter.enable());
+ break;
+ case BluetoothAdapter.STATE_TURNING_ON:
+ assertFalse(adapter.isEnabled());
+ mask = 0; // Don't check for received intents since we might have missed them.
+ break;
+ default:
+ fail("enable() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < ENABLE_TIMEOUT) {
+ state = adapter.getState();
+ if (state == BluetoothAdapter.STATE_ON) {
+ assertTrue(adapter.isEnabled());
+ if ((mReceiver.getFiredFlags() & mask) == mask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("enable() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ } else {
+ assertFalse(adapter.isEnabled());
+ assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("enable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
+ state, BluetoothAdapter.STATE_ON, firedFlags, mask));
+ }
+
+ public void disable(BluetoothAdapter adapter) {
+ int mask = STATE_TURNING_OFF_FLAG | STATE_OFF_FLAG | SCAN_MODE_NONE_FLAG;
+ mReceiver.resetFiredFlags();
+
+ int state = adapter.getState();
+ switch (state) {
+ case BluetoothAdapter.STATE_OFF:
+ assertFalse(adapter.isEnabled());
+ return;
+ case BluetoothAdapter.STATE_ON:
+ assertTrue(adapter.isEnabled());
+ assertTrue(adapter.disable());
+ break;
+ case BluetoothAdapter.STATE_TURNING_ON:
+ assertFalse(adapter.isEnabled());
+ assertTrue(adapter.disable());
+ break;
+ case BluetoothAdapter.STATE_TURNING_OFF:
+ assertFalse(adapter.isEnabled());
+ mask = 0; // Don't check for received intents since we might have missed them.
+ break;
+ default:
+ fail("disable() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < DISABLE_TIMEOUT) {
+ state = adapter.getState();
+ if (state == BluetoothAdapter.STATE_OFF) {
+ assertFalse(adapter.isEnabled());
+ if ((mReceiver.getFiredFlags() & mask) == mask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("disable() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ } else {
+ assertFalse(adapter.isEnabled());
+ assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("disable() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x)",
+ state, BluetoothAdapter.STATE_OFF, firedFlags, mask));
+ }
+
+ public void discoverable(BluetoothAdapter adapter) {
+ int mask = SCAN_MODE_CONNECTABLE_DISCOVERABLE_FLAG;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("discoverable() bluetooth not enabled");
+ }
+
+ int scanMode = adapter.getScanMode();
+ if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+ return;
+ }
+
+ assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+ assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE));
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
+ scanMode = adapter.getScanMode();
+ if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
+ if ((mReceiver.getFiredFlags() & mask) == mask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("discoverable() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ } else {
+ assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE);
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("discoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
+ + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
+ firedFlags, mask));
+ }
+
+ public void undiscoverable(BluetoothAdapter adapter) {
+ int mask = SCAN_MODE_CONNECTABLE_FLAG;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("undiscoverable() bluetooth not enabled");
+ }
+
+ int scanMode = adapter.getScanMode();
+ if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
+ return;
+ }
+
+ assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+ assertTrue(adapter.setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE));
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < SET_SCAN_MODE_TIMEOUT) {
+ scanMode = adapter.getScanMode();
+ if (scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE) {
+ if ((mReceiver.getFiredFlags() & mask) == mask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("undiscoverable() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ } else {
+ assertEquals(scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("undiscoverable() timeout: scanMode=%d (expected %d), flags=0x%x "
+ + "(expected 0x%x)", scanMode, BluetoothAdapter.SCAN_MODE_CONNECTABLE, firedFlags,
+ mask));
+ }
+
+ public void startScan(BluetoothAdapter adapter) {
+ int mask = DISCOVERY_STARTED_FLAG;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("startScan() bluetooth not enabled");
+ }
+
+ if (adapter.isDiscovering()) {
+ return;
+ }
+
+ assertTrue(adapter.startDiscovery());
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < START_DISCOVERY_TIMEOUT) {
+ if (adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("startScan() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("startScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
+ adapter.isDiscovering(), firedFlags, mask));
+ }
+
+ public void stopScan(BluetoothAdapter adapter) {
+ int mask = DISCOVERY_FINISHED_FLAG;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("stopScan() bluetooth not enabled");
+ }
+
+ if (!adapter.isDiscovering()) {
+ return;
+ }
+
+ // TODO: put assertTrue() around cancelDiscovery() once it starts returning true.
+ adapter.cancelDiscovery();
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < CANCEL_DISCOVERY_TIMEOUT) {
+ if (!adapter.isDiscovering() && ((mReceiver.getFiredFlags() & mask) == mask)) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("stopScan() completed in %d ms",
+ (System.currentTimeMillis() - s)));
+ return;
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("stopScan() timeout: isDiscovering=%b, flags=0x%x (expected 0x%x)",
+ adapter.isDiscovering(), firedFlags, mask));
+
+ }
+
+ public void pair(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PAIR_STATE_FLAG;
+ int pairMask = PAIR_STATE_BONDING | PAIR_STATE_BONDED;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("pair() bluetooth not enabled");
+ }
+
+ int state = device.getBondState();
+ switch (state) {
+ case BluetoothDevice.BOND_BONDED:
+ assertTrue(adapter.getBondedDevices().contains(device));
+ return;
+ case BluetoothDevice.BOND_BONDING:
+ // Don't check for received intents since we might have missed them.
+ mask = pairMask = 0;
+ break;
+ case BluetoothDevice.BOND_NONE:
+ assertFalse(adapter.getBondedDevices().contains(device));
+ assertTrue(device.createBond());
+ break;
+ default:
+ fail("pair() invalide state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < PAIR_TIMEOUT) {
+ state = device.getBondState();
+ if (state == BluetoothDevice.BOND_BONDED) {
+ assertTrue(adapter.getBondedDevices().contains(device));
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
+ writeOutput(String.format("pair() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int pairFiredFlags = mReceiver.getPairFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("pair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
+ + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
+ mask, pairFiredFlags, pairMask));
+ }
+
+ public void unpair(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PAIR_STATE_FLAG;
+ int pairMask = PAIR_STATE_NONE;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("unpair() bluetooth not enabled");
+ }
+
+ int state = device.getBondState();
+ switch (state) {
+ case BluetoothDevice.BOND_BONDED:
+ assertTrue(adapter.getBondedDevices().contains(device));
+ assertTrue(device.removeBond());
+ break;
+ case BluetoothDevice.BOND_BONDING:
+ assertTrue(device.removeBond());
+ break;
+ case BluetoothDevice.BOND_NONE:
+ assertFalse(adapter.getBondedDevices().contains(device));
+ return;
+ default:
+ fail("unpair() invalid state: state=" + state);
+ }
+
+ assertTrue(device.removeBond());
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) {
+ if (device.getBondState() == BluetoothDevice.BOND_NONE) {
+ assertFalse(adapter.getBondedDevices().contains(device));
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) {
+ writeOutput(String.format("unpair() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int pairFiredFlags = mReceiver.getPairFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("unpair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), "
+ + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags,
+ mask, pairFiredFlags, pairMask));
+ }
+
+ public void connectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PROFILE_A2DP_FLAG;
+ int a2dpMask1 = A2DP_STATE_CONNECTING | A2DP_STATE_CONNECTED | A2DP_STATE_PLAYING;
+ int a2dpMask2 = a2dpMask1 ^ A2DP_STATE_CONNECTED;
+ int a2dpMask3 = a2dpMask1 ^ A2DP_STATE_PLAYING;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("connectA2dp() bluetooth not enabled");
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail("connectA2dp() device not paired: device=" + device);
+ }
+
+ int state = mA2dp.getSinkState(device);
+ switch (state) {
+ case BluetoothA2dp.STATE_CONNECTED:
+ case BluetoothA2dp.STATE_PLAYING:
+ assertTrue(mA2dp.isSinkConnected(device));
+ return;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ case BluetoothA2dp.STATE_DISCONNECTED:
+ assertFalse(mA2dp.isSinkConnected(device));
+ assertTrue(mA2dp.connectSink(device));
+ break;
+ case BluetoothA2dp.STATE_CONNECTING:
+ assertFalse(mA2dp.isSinkConnected(device));
+ // Don't check for received intents since we might have missed them.
+ mask = a2dpMask1 = a2dpMask2 = a2dpMask3 = 0;
+ break;
+ default:
+ fail("connectA2dp() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < CONNECT_A2DP_TIMEOUT) {
+ state = mA2dp.getSinkState(device);
+ if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) {
+ assertTrue(mA2dp.isSinkConnected(device));
+ // Check whether STATE_CONNECTING and (STATE_CONNECTED or STATE_PLAYING) intents
+ // have fired if we are checking if intents should be fired.
+ int firedFlags = mReceiver.getFiredFlags();
+ int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && ((a2dpFiredFlags & a2dpMask1) == a2dpMask1
+ || (a2dpFiredFlags & a2dpMask2) == a2dpMask2
+ || (a2dpFiredFlags & a2dpMask3) == a2dpMask3)) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("connectA2dp() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("connectA2dp() timeout: state=%d (expected %d or %d), "
+ + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x or 0x%x or 0x%x)",
+ state, BluetoothHeadset.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING, firedFlags,
+ mask, a2dpFiredFlags, a2dpMask1, a2dpMask2, a2dpMask3));
+ }
+
+ public void disconnectA2dp(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PROFILE_A2DP_FLAG;
+ int a2dpMask = A2DP_STATE_DISCONNECTING | A2DP_STATE_DISCONNECTED;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("disconnectA2dp() bluetooth not enabled");
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail("disconnectA2dp() device not paired: device=" + device);
+ }
+
+ int state = mA2dp.getSinkState(device);
+ switch (state) {
+ case BluetoothA2dp.STATE_DISCONNECTED:
+ assertFalse(mA2dp.isSinkConnected(device));
+ return;
+ case BluetoothA2dp.STATE_CONNECTED:
+ case BluetoothA2dp.STATE_PLAYING:
+ assertTrue(mA2dp.isSinkConnected(device));
+ assertTrue(mA2dp.disconnectSink(device));
+ break;
+ case BluetoothA2dp.STATE_CONNECTING:
+ assertFalse(mA2dp.isSinkConnected(device));
+ assertTrue(mA2dp.disconnectSink(device));
+ break;
+ case BluetoothA2dp.STATE_DISCONNECTING:
+ assertFalse(mA2dp.isSinkConnected(device));
+ // Don't check for received intents since we might have missed them.
+ mask = a2dpMask = 0;
+ break;
+ default:
+ fail("disconnectA2dp() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < DISCONNECT_A2DP_TIMEOUT) {
+ state = mA2dp.getSinkState(device);
+ if (state == BluetoothA2dp.STATE_DISCONNECTED) {
+ assertFalse(mA2dp.isSinkConnected(device));
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && (mReceiver.getA2dpFiredFlags() & a2dpMask) == a2dpMask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("disconnectA2dp() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int a2dpFiredFlags = mReceiver.getA2dpFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("disconnectA2dp() timeout: state=%d (expected %d), "
+ + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x)", state,
+ BluetoothA2dp.STATE_DISCONNECTED, firedFlags, mask, a2dpFiredFlags, a2dpMask));
+ }
+
+ public void connectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PROFILE_HEADSET_FLAG;
+ int headsetMask = HEADSET_STATE_CONNECTING | HEADSET_STATE_CONNECTED;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("connectHeadset() bluetooth not enabled");
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail("connectHeadset() device not paired: device=" + device);
+ }
+
+ while (!mHeadsetServiceListener.isConnected()) {
+ sleep(POLL_TIME);
+ }
+
+ int state = mHeadset.getState(device);
+ switch (state) {
+ case BluetoothHeadset.STATE_CONNECTED:
+ assertTrue(mHeadset.isConnected(device));
+ return;
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ assertFalse(mHeadset.isConnected(device));
+ mHeadset.connectHeadset(device);
+ break;
+ case BluetoothHeadset.STATE_CONNECTING:
+ assertFalse(mHeadset.isConnected(device));
+ // Don't check for received intents since we might have missed them.
+ mask = headsetMask = 0;
+ break;
+ case BluetoothHeadset.STATE_ERROR:
+ fail("connectHeadset() error state");
+ break;
+ default:
+ fail("connectHeadset() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < CONNECT_HEADSET_TIMEOUT) {
+ state = mHeadset.getState(device);
+ if (state == BluetoothHeadset.STATE_CONNECTED) {
+ assertTrue(mHeadset.isConnected(device));
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("connectHeadset() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("connectHeadset() timeout: state=%d (expected %d), "
+ + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
+ BluetoothHeadset.STATE_CONNECTED, firedFlags, mask, headsetFiredFlags,
+ headsetMask));
+ }
+
+ public void disconnectHeadset(BluetoothAdapter adapter, BluetoothDevice device) {
+ int mask = PROFILE_HEADSET_FLAG;
+ int headsetMask = HEADSET_STATE_DISCONNECTED;
+ mReceiver.resetFiredFlags();
+
+ if (!adapter.isEnabled()) {
+ fail("disconnectHeadset() bluetooth not enabled");
+ }
+
+ if (!adapter.getBondedDevices().contains(device)) {
+ fail("disconnectHeadset() device not paired: device=" + device);
+ }
+
+ while (!mHeadsetServiceListener.isConnected()) {
+ sleep(POLL_TIME);
+ }
+
+ int state = mHeadset.getState(device);
+ switch (state) {
+ case BluetoothHeadset.STATE_CONNECTED:
+ mHeadset.disconnectHeadset(device);
+ break;
+ case BluetoothHeadset.STATE_CONNECTING:
+ mHeadset.disconnectHeadset(device);
+ break;
+ case BluetoothHeadset.STATE_DISCONNECTED:
+ return;
+ case BluetoothHeadset.STATE_ERROR:
+ fail("disconnectHeadset() error state");
+ break;
+ default:
+ fail("disconnectHeadset() invalid state: state=" + state);
+ }
+
+ long s = System.currentTimeMillis();
+ while (System.currentTimeMillis() - s < DISCONNECT_HEADSET_TIMEOUT) {
+ state = mHeadset.getState(device);
+ if (state == BluetoothHeadset.STATE_DISCONNECTED) {
+ assertFalse(mHeadset.isConnected(device));
+ if ((mReceiver.getFiredFlags() & mask) == mask
+ && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) {
+ mReceiver.resetFiredFlags();
+ writeOutput(String.format("disconnectHeadset() completed in %d ms: device=%s",
+ (System.currentTimeMillis() - s), device));
+ return;
+ }
+ }
+ sleep(POLL_TIME);
+ }
+
+ int firedFlags = mReceiver.getFiredFlags();
+ int headsetFiredFlags = mReceiver.getHeadsetFiredFlags();
+ mReceiver.resetFiredFlags();
+ fail(String.format("disconnectHeadset() timeout: state=%d (expected %d), "
+ + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state,
+ BluetoothHeadset.STATE_DISCONNECTED, firedFlags, mask, headsetFiredFlags,
+ headsetMask));
+ }
+
+ public void writeOutput(String s) {
+ Log.i(mTag, s);
+ if (mOutputWriter == null) {
+ return;
+ }
+ try {
+ mOutputWriter.write(s + "\n");
+ mOutputWriter.flush();
+ } catch (IOException e) {
+ Log.w(mTag, "Could not write to output file", e);
+ }
+ }
+
+ private void sleep(long time) {
+ try {
+ Thread.sleep(time);
+ } catch (InterruptedException e) {
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
deleted file mode 100644
index b9e9875..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java
+++ /dev/null
@@ -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();
- }
- }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
deleted file mode 100644
index 951d0d8..0000000
--- a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java
+++ /dev/null
@@ -1,221 +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.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.RawContacts;
-import android.test.mock.MockContentProvider;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockCursor;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/* package */ public class ExportTestResolver extends MockContentResolver {
- 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>();
-
- public ExportTestProvider(TestCase testCase) {
- mTestCase = testCase;
- }
-
- public ContactEntry buildInputEntry() {
- ContactEntry contactEntry = new ContactEntry();
- mContactEntryList.add(contactEntry);
- return contactEntry;
- }
-
- /**
- * <p>
- * An old method which had existed but was removed from ContentResolver.
- * </p>
- * <p>
- * 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.
- * </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,
- String selection, String[] selectionArgs, String sortOrder) {
- mTestCase.assertTrue(uri != null);
- mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
- final String authority = uri.getAuthority();
- mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
- mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
- mTestCase.assertEquals(1, selectionArgs.length);
- final int id = Integer.parseInt(selectionArgs[0]);
- mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
-
- return new MockEntityIterator(mContactEntryList.get(id).getList());
- }
-
- @Override
- public Cursor query(Uri uri,String[] projection,
- String selection, String[] selectionArgs, String sortOrder) {
- mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
- // In this test, following arguments are not supported.
- mTestCase.assertNull(selection);
- mTestCase.assertNull(selectionArgs);
- mTestCase.assertNull(sortOrder);
-
- return new MockCursor() {
- int mCurrentPosition = -1;
-
- @Override
- public int getCount() {
- return mContactEntryList.size();
- }
-
- @Override
- public boolean moveToFirst() {
- mCurrentPosition = 0;
- return true;
- }
-
- @Override
- public boolean moveToNext() {
- if (mCurrentPosition < mContactEntryList.size()) {
- mCurrentPosition++;
- return true;
- } else {
- return false;
- }
- }
-
- @Override
- public boolean isBeforeFirst() {
- return mCurrentPosition < 0;
- }
-
- @Override
- public boolean isAfterLast() {
- return mCurrentPosition >= mContactEntryList.size();
- }
-
- @Override
- public int getColumnIndex(String columnName) {
- mTestCase.assertEquals(Contacts._ID, columnName);
- return 0;
- }
-
- @Override
- public int getInt(int columnIndex) {
- mTestCase.assertEquals(0, columnIndex);
- mTestCase.assertTrue(mCurrentPosition >= 0
- && mCurrentPosition < mContactEntryList.size());
- return mCurrentPosition;
- }
-
- @Override
- public String getString(int columnIndex) {
- return String.valueOf(getInt(columnIndex));
- }
-
- @Override
- public void close() {
- }
- };
- }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
index 2962a926..2bec462 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java
@@ -17,7 +17,10 @@
package android.pim.vcard;
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.Event;
import android.provider.ContactsContract.CommonDataKinds.Im;
@@ -31,8 +34,6 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
import java.util.Arrays;
/**
@@ -56,7 +57,6 @@
}
private void testStructuredNameBasic(int vcardType) {
- final boolean isV30 = VCardConfig.isV30(vcardType);
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "AppropriateFamilyName")
@@ -64,28 +64,15 @@
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
- .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
- .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
- .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle");
+ .put(StructuredName.DISPLAY_NAME, "DISPLAY NAME");
- PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
- .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");
-
- if (isV30) {
- elem.addExpectedNode("SORT-STRING",
- "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
- + "AppropriatePhoneticFamily");
- }
+ .addExpectedNodeWithOrder("FN", "DISPLAY NAME");
}
public void testStructuredNameBasicV21() {
@@ -96,6 +83,10 @@
testStructuredNameBasic(V30);
}
+ public void testStructuredNameBasicV40() {
+ testStructuredNameBasic(V40);
+ }
+
/**
* Test that only "primary" StructuredName is emitted, so that our vCard file
* will not confuse the external importer, assuming there may be some importer
@@ -103,18 +94,15 @@
* Note that more than one "N", "FN", etc. properties are acceptable in vCard spec.
*/
private void testStructuredNameUsePrimaryCommon(int vcardType) {
- final boolean isV30 = (vcardType == V30);
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.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1")
- .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1")
- .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1");
+ .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName1");
// With "IS_PRIMARY=1". This is what we should use.
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
@@ -123,41 +111,30 @@
.put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName")
.put(StructuredName.PREFIX, "AppropriatePrefix")
.put(StructuredName.SUFFIX, "AppropriateSuffix")
- .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily")
- .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven")
- .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle")
+ .put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName")
.put(StructuredName.IS_PRIMARY, 1);
// 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)
.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.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2")
- .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2")
- .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2")
+ .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName2")
.put(StructuredName.IS_PRIMARY, 1);
- PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
+ mVerifier.addPropertyNodesVerifierElem()
.addExpectedNodeWithOrder("N",
"AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
+ "AppropriatePrefix;AppropriateSuffix",
Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
"AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
- .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");
-
- if (isV30) {
- elem.addExpectedNode("SORT-STRING",
- "AppropriatePhoneticGiven AppropriatePhoneticMiddle "
- + "AppropriatePhoneticFamily");
- }
+ .addExpectedNodeWithOrder("FN", "AppropriateDisplayName");
}
public void testStructuredNameUsePrimaryV21() {
@@ -168,14 +145,152 @@
testStructuredNameUsePrimaryCommon(V30);
}
+ public void testStructuredNameUsePrimaryV40() {
+ testStructuredNameUsePrimaryCommon(V40);
+ }
+
/**
* Tests that only "super primary" StructuredName is emitted.
* See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}.
*/
private void testStructuredNameUseSuperPrimaryCommon(int vcardType) {
- final boolean isV30 = (vcardType == V30);
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)
.put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1")
.put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1")
@@ -221,32 +336,50 @@
.put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3")
.put(StructuredName.IS_PRIMARY, 1);
- PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("N",
- "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;"
- + "AppropriatePrefix;AppropriateSuffix",
- Arrays.asList("AppropriateFamilyName", "AppropriateGivenName",
- "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"))
- .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");
-
- if (isV30) {
- elem.addExpectedNode("SORT-STRING",
- "AppropriatePhoneticGiven AppropriatePhoneticMiddle"
- + " AppropriatePhoneticFamily");
+ 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",
+ "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() {
- testStructuredNameUseSuperPrimaryCommon(V21);
+ public void testStructuredNameComplicatedV21() {
+ testStructuredNameComplicatedCommon(V21);
}
- public void testStructuredNameUseSuperPrimaryV30() {
- testStructuredNameUseSuperPrimaryCommon(V30);
+ public void testStructuredNameComplicatedV30() {
+ testStructuredNameComplicatedCommon(V30);
+ }
+
+ public void testStructuredNameComplicatedV40() {
+ testStructuredNameComplicatedCommon(V40);
}
public void testNickNameV30() {
@@ -258,6 +391,15 @@
.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) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
@@ -275,6 +417,10 @@
testPhoneBasicCommon(V30);
}
+ public void testPhoneBasicV40() {
+ testPhoneBasicCommon(V40);
+ }
+
public void testPhoneRefrainFormatting() {
mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING);
mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE)
@@ -367,6 +513,10 @@
testPhoneVariousTypeSupport(V30);
}
+ public void testPhoneVariousTypeSupportV40() {
+ testPhoneVariousTypeSupport(V40);
+ }
+
/**
* Tests that "PREF"s are emitted appropriately.
*/
@@ -403,6 +553,10 @@
testPhonePrefHandlingCommon(V30);
}
+ public void testPhonePrefHandlingV40() {
+ testPhonePrefHandlingCommon(V40);
+ }
+
private void testMiscPhoneTypeHandling(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -438,7 +592,7 @@
.put(Phone.TYPE, Phone.TYPE_CUSTOM)
.put(Phone.LABEL, "invalid");
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
- if (VCardConfig.isV30(vcardType)) {
+ if (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType)) {
// vCard 3.0 accepts "invalid". Also stop using toUpper()
elem.addExpectedNode("TEL", "1", new TypeSet("Modem"))
.addExpectedNode("TEL", "2", new TypeSet("MSG"))
@@ -468,6 +622,10 @@
testMiscPhoneTypeHandling(V30);
}
+ public void testPhoneTypeHandlingV40() {
+ testMiscPhoneTypeHandling(V40);
+ }
+
private void testEmailBasicCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE)
@@ -484,6 +642,10 @@
testEmailBasicCommon(V30);
}
+ public void testEmailBasicV40() {
+ testEmailBasicCommon(V40);
+ }
+
private void testEmailVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -514,6 +676,10 @@
testEmailVariousTypeSupportCommon(V30);
}
+ public void testEmailVariousTypeSupportV40() {
+ testEmailVariousTypeSupportCommon(V40);
+ }
+
private void testEmailPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -538,6 +704,10 @@
testEmailPrefHandlingCommon(V30);
}
+ public void testEmailPrefHandlingV40() {
+ testEmailPrefHandlingCommon(V40);
+ }
+
private void testPostalAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -569,6 +739,10 @@
testPostalAddressCommon(V30);
}
+ public void testPostalAddressV40() {
+ testPostalAddressCommon(V40);
+ }
+
private void testPostalAddressNonNeighborhood(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -586,6 +760,10 @@
testPostalAddressNonNeighborhood(V30);
}
+ public void testPostalAddressNonNeighborhoodV40() {
+ testPostalAddressNonNeighborhood(V40);
+ }
+
private void testPostalAddressNonCity(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -603,6 +781,10 @@
testPostalAddressNonCity(V30);
}
+ public void testPostalAddressNonCityV40() {
+ testPostalAddressNonCity(V40);
+ }
+
private void testPostalOnlyWithFormattedAddressCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
@@ -623,6 +805,10 @@
testPostalOnlyWithFormattedAddressCommon(V30);
}
+ public void testPostalOnlyWithFormattedAddressV40() {
+ testPostalOnlyWithFormattedAddressCommon(V40);
+ }
+
/**
* Tests that the vCard composer honors formatted data when it is available
* even when it is partial.
@@ -648,6 +834,10 @@
testPostalWithBothStructuredAndFormattedCommon(V30);
}
+ public void testPostalWithBothStructuredAndFormattedV40() {
+ testPostalWithBothStructuredAndFormattedCommon(V40);
+ }
+
private void testOrganizationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -685,6 +875,10 @@
testOrganizationCommon(V30);
}
+ public void testOrganizationV40() {
+ testOrganizationCommon(V40);
+ }
+
private void testImVariousTypeSupportCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -737,6 +931,10 @@
testImVariousTypeSupportCommon(V30);
}
+ public void testImBasicV40() {
+ testImVariousTypeSupportCommon(V40);
+ }
+
private void testImPrefHandlingCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -762,6 +960,10 @@
testImPrefHandlingCommon(V30);
}
+ public void testImPrefHandlingV40() {
+ testImPrefHandlingCommon(V40);
+ }
+
private void testWebsiteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -786,6 +988,10 @@
testWebsiteCommon(V30);
}
+ public void testWebsiteV40() {
+ testWebsiteCommon(V40);
+ }
+
private String getAndroidPropValue(final String mimeType, String value, Integer type) {
return getAndroidPropValue(mimeType, value, type, null);
}
@@ -839,6 +1045,10 @@
testEventCommon(V30);
}
+ public void testEventV40() {
+ testEventCommon(V40);
+ }
+
private void testNoteCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
@@ -860,8 +1070,13 @@
testNoteCommon(V30);
}
+ public void testNoteV40() {
+ testNoteCommon(V40);
+ }
+
private void testPhotoCommon(int vcardType) {
- final boolean isV30 = vcardType == V30;
+ final boolean useB =
+ (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType));
mVerifier.initForExportTest(vcardType);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
@@ -870,7 +1085,7 @@
.put(Photo.PHOTO, sPhotoByteArray);
ContentValues contentValuesForPhoto = new ContentValues();
- contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64"));
+ contentValuesForPhoto.put("ENCODING", (useB ? "b" : "BASE64"));
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "PhotoTest")
.addExpectedNode("N", "PhotoTest;;;;",
@@ -887,6 +1102,10 @@
testPhotoCommon(V30);
}
+ public void testPhotoV40() {
+ testPhotoCommon(V40);
+ }
+
private void testRelationCommon(int vcardType) {
mVerifier.initForExportTest(vcardType);
mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE)
@@ -957,18 +1176,22 @@
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored.
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)
.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)
.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)
- .put(StructuredName.FAMILY_NAME, "family4");
+ .put(StructuredName.FAMILY_NAME, "family4")
+ .put(StructuredName.DISPLAY_NAME, "display");
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("N", Arrays.asList("family2", "", "", "", ""))
- .addExpectedNode("FN", "family2");
+ .addExpectedNode("FN", "display");
}
public void testPickUpNonEmptyContentValuesV21() {
@@ -978,4 +1201,23 @@
public void testPickUpNonEmptyContentValuesV30() {
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"));
+ }
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
index e0e1f87..09e2914 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java
@@ -15,11 +15,17 @@
*/
package android.pim.vcard;
+import com.android.frameworks.coretests.R;
+
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
-import android.provider.ContactsContract.Data;
+import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet;
+import android.pim.vcard.test_utils.VCardTestsBase;
+import android.pim.vcard.test_utils.ContentValuesVerifier;
+import android.pim.vcard.test_utils.ContentValuesVerifierElem;
import android.provider.ContactsContract.CommonDataKinds.Email;
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.Organization;
import android.provider.ContactsContract.CommonDataKinds.Phone;
@@ -27,9 +33,7 @@
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.provider.ContactsContract.CommonDataKinds.Website;
-
-import com.android.frameworks.coretests.R;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
+import android.provider.ContactsContract.Data;
import java.util.Arrays;
@@ -405,12 +409,12 @@
public void testV21SimpleCase1_Parsing() {
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", ""));
}
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()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando")
@@ -419,7 +423,7 @@
}
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()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "Ando")
@@ -431,7 +435,7 @@
}
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()
.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.DISPLAY_NAME, "Ando Roid");
@@ -454,7 +458,6 @@
public void testV21BackslashCase_Parsing() {
mVerifier.initForImportTest(V21, R.raw.v21_backslash);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;",
Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", ""))
.addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\");
@@ -549,11 +552,11 @@
public void testV21ComplicatedCase_Parsing() {
mVerifier.initForImportTest(V21, R.raw.v21_complicated);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao",
Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao"))
.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"))
.addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!")
.addExpectedNodeWithOrder("TITLE", "Shrimp Man")
@@ -583,7 +586,8 @@
.addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com",
new TypeSet("PREF", "INTERNET"))
.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",
"Now's the time for all folk to come to the aid of their country.",
null, null, mContentValuesForQP, null, null)
@@ -677,12 +681,24 @@
.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() {
mVerifier.initForImportTest(V30, R.raw.v30_simple);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "3.0")
.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",
Arrays.asList("Open", "Handset", " Alliance"))
.addExpectedNodeWithOrder("SORT-STRING", "android")
@@ -717,10 +733,8 @@
// Though Japanese careers append ";;;;" at the end of the value of "SOUND",
// vCard 2.1/3.0 specification does not allow multiple values.
// Do not need to handle it as multiple values.
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_japanese_1);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null)
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""),
null, mContentValuesForSJis, null, null)
@@ -752,37 +766,35 @@
/**
* 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() {
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
- * {@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() {
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
- * {@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.
*/
public void testV21Japanese1_Type_Japanese_Utf8() {
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() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_japanese_2);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;",
Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031",
"", "", ""),
@@ -838,10 +850,8 @@
}
public void testV21MultipleEntryCase_Parse() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_multiple_entry);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""),
null, mContentValuesForSJis, null, null)
@@ -854,7 +864,6 @@
.addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL"))
.addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME"));
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""),
null, mContentValuesForSJis, null, null)
@@ -867,7 +876,6 @@
.addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY"))
.addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL"));
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;",
Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""),
null, mContentValuesForSJis, null, null)
@@ -882,8 +890,7 @@
}
public void testV21MultipleEntryCase() {
- mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS,
- R.raw.v21_multiple_entry);
+ mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033")
@@ -957,7 +964,6 @@
ContentValues contentValuesForValue = new ContentValues();
contentValuesForValue.put("VALUE", "DATE");
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "2.1")
.addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", ""))
.addExpectedNodeWithOrder("FN", "Example")
.addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue)
@@ -974,6 +980,9 @@
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_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() {
@@ -988,16 +997,15 @@
}
public void testPagerV30_Parse() {
- mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ mVerifier.initForImportTest(V30, R.raw.v30_pager);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "3.0")
.addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
.addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com",
new TypeSet("WORK", "MSG", "PAGER"));
}
public void testPagerV30() {
- mVerifier.initForImportTest(V30, R.raw.v30_comma_separated);
+ mVerifier.initForImportTest(V30, R.raw.v30_pager);
ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem();
elem.addExpected(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "F")
@@ -1012,7 +1020,6 @@
public void testMultiBytePropV30_Parse() {
mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param);
mVerifier.addPropertyNodesVerifierElem()
- .addExpectedNodeWithOrder("VERSION", "3.0")
.addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""))
.addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39"));
}
@@ -1030,4 +1037,72 @@
.put(Phone.LABEL, "\u8D39")
.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");
+ }
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
index 5b60342..8a99419 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java
@@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
+import android.pim.vcard.test_utils.ContactEntry;
+import android.pim.vcard.test_utils.ContentValuesBuilder;
+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.Nickname;
import android.provider.ContactsContract.CommonDataKinds.Note;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
-import android.pim.vcard.PropertyNodesVerifierElem.TypeSet;
-
import java.util.Arrays;
public class VCardJapanizationTests extends VCardTestsBase {
@@ -39,7 +40,7 @@
.put(StructuredName.PREFIX, "Dr.")
.put(StructuredName.SUFFIX, "Ph.D");
ContentValues contentValues =
- (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8);
+ (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null);
mVerifier.addPropertyNodesVerifierElem()
.addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D",
contentValues)
@@ -50,15 +51,15 @@
}
public void testNameUtf8V21() {
- testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testNameUtf8V30() {
- testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testNameShiftJis() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -80,7 +81,7 @@
* DoCoMo phones require all name elements should be in "family name" field.
*/
public void testNameDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069")
@@ -105,8 +106,8 @@
.addExpectedNode("X-DCM-HMN-MODE", "");
}
- private void testPhoneticNameCommon(int vcardType) {
- mVerifier.initForExportTest(vcardType);
+ private void testPhoneticNameCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -114,10 +115,10 @@
.put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046");
final ContentValues contentValues =
- (VCardConfig.usesShiftJis(vcardType) ?
- (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
- mContentValuesForQPAndSJis) :
- (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8));
+ ("SHIFT_JIS".equalsIgnoreCase(charset) ?
+ (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
+ mContentValuesForSJis) :
+ (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060",
contentValues)
@@ -126,7 +127,7 @@
contentValues)
.addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046",
contentValues);
- if (VCardConfig.isV30(vcardType)) {
+ if (!VCardConfig.isVersion21(vcardType)) {
elem.addExpectedNode("SORT-STRING",
"\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046",
contentValues);
@@ -142,23 +143,23 @@
}
public void testPhoneticNameForJapaneseV21Utf8() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV21Sjis() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
public void testPhoneticNameForJapaneseV30Utf8() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null);
}
public void testPhoneticNameForJapaneseV30SJis() {
- testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS);
+ testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS");
}
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();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -182,7 +183,7 @@
}
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();
entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE)
.put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060")
@@ -198,8 +199,8 @@
.put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73");
}
- private void testPostalAddressWithJapaneseCommon(int vcardType) {
- mVerifier.initForExportTest(vcardType);
+ private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) {
+ mVerifier.initForExportTest(vcardType, charset);
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107")
@@ -214,11 +215,11 @@
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
.put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A");
- ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ?
- (VCardConfig.isV30(vcardType) ? mContentValuesForSJis :
- mContentValuesForQPAndSJis) :
- (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 :
- mContentValuesForQPAndUtf8));
+ ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ?
+ (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis :
+ mContentValuesForSJis) :
+ (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 :
+ mContentValuesForUtf8));
PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName();
// LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is
@@ -240,7 +241,7 @@
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME);
}
public void testPostalAddresswithJapaneseV21() {
- testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS);
+ testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS");
}
/**
@@ -248,7 +249,7 @@
* Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM
*/
public void testPostalAdrressForDoCoMo_1() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK)
@@ -276,7 +277,7 @@
}
public void testPostalAdrressForDoCoMo_2() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER)
@@ -301,7 +302,7 @@
}
public void testPostalAdrressForDoCoMo_3() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM)
@@ -329,7 +330,7 @@
* Verifies the vCard exporter tolerates null TYPE.
*/
public void testPostalAdrressForDoCoMo_4() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE)
.put(StructuredPostal.POBOX, "1");
@@ -371,15 +372,15 @@
}
public void testJapanesePhoneNumberV21_1() {
- testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8);
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE);
}
public void testJapanesePhoneNumberV30() {
- testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8);
+ testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE);
}
public void testJapanesePhoneNumberDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Phone.CONTENT_ITEM_TYPE)
.put(Phone.NUMBER, "0312341234")
@@ -399,7 +400,7 @@
}
public void testNoteDoCoMo() {
- mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO);
+ mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS");
ContactEntry entry = mVerifier.addInputEntry();
entry.addContentValues(Note.CONTENT_ITEM_TYPE)
.put(Note.NOTE, "note1");
@@ -421,7 +422,7 @@
}
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)
.put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC");
mVerifier.addPropertyNodesVerifierElemWithEmptyName()
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java b/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java
new file mode 100644
index 0000000..a3a4393
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java
new file mode 100644
index 0000000..3ff5cf7
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java
@@ -0,0 +1,98 @@
+/*
+ * 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.frameworks.coretests.R;
+
+import android.pim.vcard.test_utils.VCardVerifier;
+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.");
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
index e805bee..251fab2 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
+++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java
@@ -13,10 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.pim.vcard;
-import android.pim.vcard.VCardUtils;
+import android.text.TextUtils;
import junit.framework.TestCase;
@@ -85,31 +84,35 @@
public void testToStringAvailableAsV30ParamValue() {
// Smoke tests.
- assertEquals("HOME", VCardUtils.toStringAvailableAsV30ParameValue("HOME"));
- assertEquals("TEL", VCardUtils.toStringAvailableAsV30ParameValue("TEL"));
- assertEquals("PAGER", VCardUtils.toStringAvailableAsV30ParameValue("PAGER"));
+ assertEquals("HOME", VCardUtils.toStringAsV30ParamValue("HOME"));
+ assertEquals("TEL", VCardUtils.toStringAsV30ParamValue("TEL"));
+ 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
assertEquals("\u4E8B\u52D9\u6240",
- VCardUtils.toStringAvailableAsV30ParameValue("\u4E8B\u52D9\u6240"));
+ VCardUtils.toStringAsV30ParamValue("\u4E8B\u52D9\u6240"));
// Reported as bug report.
- assertEquals("\u8D39", VCardUtils.toStringAvailableAsV30ParameValue("\u8D39"));
+ assertEquals("\u8D39", VCardUtils.toStringAsV30ParamValue("\u8D39"));
assertEquals("\"comma,separated\"",
- VCardUtils.toStringAvailableAsV30ParameValue("comma,separated"));
+ VCardUtils.toStringAsV30ParamValue("comma,separated"));
assertEquals("\"colon:aware\"",
- VCardUtils.toStringAvailableAsV30ParameValue("colon:aware"));
+ VCardUtils.toStringAsV30ParamValue("colon:aware"));
// CTL characters.
assertEquals("CTLExample",
- VCardUtils.toStringAvailableAsV30ParameValue("CTL\u0001Example"));
+ VCardUtils.toStringAsV30ParamValue("CTL\u0001Example"));
+ assertTrue(TextUtils.isEmpty(
+ VCardUtils.toStringAsV30ParamValue("\u0001\u0002\u0003")));
// DQUOTE must be removed.
assertEquals("quoted",
- VCardUtils.toStringAvailableAsV30ParameValue("\"quoted\""));
+ VCardUtils.toStringAsV30ParamValue("\"quoted\""));
// DQUOTE must be removed basically, but we should detect a space, which
// require us to use DQUOTE again.
// Right-side has one more illegal dquote to test quote-handle code thoroughly.
assertEquals("\"Already quoted\"",
- VCardUtils.toStringAvailableAsV30ParameValue("\"Already quoted\"\""));
+ VCardUtils.toStringAsV30ParamValue("\"Already quoted\"\""));
}
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
deleted file mode 100644
index 3cb5b9b..0000000
--- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java
+++ /dev/null
@@ -1,313 +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.content.ContentProvider;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.EntityIterator;
-import android.net.Uri;
-import android.pim.vcard.VCardComposer;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardInterpreterCollection;
-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.mock.MockContext;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.reflect.Method;
-import java.util.Arrays;
-
-/* package */ class CustomMockContext extends MockContext {
- final ContentResolver mResolver;
- public CustomMockContext(ContentResolver resolver) {
- mResolver = resolver;
- }
-
- @Override
- public ContentResolver getContentResolver() {
- return mResolver;
- }
-}
-
-/* package */ class VCardVerifier {
- private static final String LOG_TAG = "VCardVerifier";
-
- private class VCardVerifierInternal implements VCardComposer.OneEntryHandler {
- public boolean onInit(Context context) {
- return true;
- }
- public boolean onEntryCreated(String vcard) {
- verifyOneVCard(vcard);
- return true;
- }
- public void onTerminate() {
- }
- }
-
- private final AndroidTestCase mTestCase;
- private final VCardVerifierInternal mVCardVerifierInternal;
- private int mVCardType;
- private boolean mIsV30;
- private boolean mIsDoCoMo;
-
- // Only one of them must be non-empty.
- private ExportTestResolver mExportTestResolver;
- private InputStream mInputStream;
-
- // To allow duplication, use list instead of set.
- // When null, we don't need to do the verification.
- private PropertyNodesVerifier mPropertyNodesVerifier;
- private LineVerifier mLineVerifier;
- private ContentValuesVerifier mContentValuesVerifier;
- private boolean mInitialized;
- private boolean mVerified = false;
-
- public VCardVerifier(AndroidTestCase androidTestCase) {
- mTestCase = androidTestCase;
- mVCardVerifierInternal = new VCardVerifierInternal();
- mExportTestResolver = null;
- mInputStream = null;
- mInitialized = false;
- mVerified = false;
- }
-
- public void initForExportTest(int vcardType) {
- 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) {
- if (mInitialized) {
- mTestCase.fail("Already initialized");
- }
- mVCardType = vcardType;
- mIsV30 = VCardConfig.isV30(vcardType);
- mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
- setInputResourceId(resId);
- mInitialized = true;
- }
-
- private void setInputResourceId(int resId) {
- InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId);
- if (inputStream == null) {
- mTestCase.fail("Wrong resId: " + resId);
- }
- setInputStream(inputStream);
- }
-
- private void setInputStream(InputStream inputStream) {
- if (mExportTestResolver != null) {
- mTestCase.fail("addInputEntry() is called.");
- } else if (mInputStream != null) {
- mTestCase.fail("InputStream is already set");
- }
- mInputStream = inputStream;
- }
-
- public ContactEntry addInputEntry() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized");
- }
- if (mInputStream != null) {
- mTestCase.fail("setInputStream is called");
- }
- return mExportTestResolver.addInputContactEntry();
- }
-
- public PropertyNodesVerifierElem addPropertyNodesVerifierElem() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized");
- }
- if (mPropertyNodesVerifier == null) {
- mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase);
- }
- PropertyNodesVerifierElem elem =
- mPropertyNodesVerifier.addPropertyNodesVerifierElem();
- elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1"));
-
- return elem;
- }
-
- public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized");
- }
- PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
- if (mIsV30) {
- elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", "");
- } else if (mIsDoCoMo) {
- elem.addExpectedNodeWithOrder("N", "");
- }
- return elem;
- }
-
- public LineVerifierElem addLineVerifierElem() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized");
- }
- if (mLineVerifier == null) {
- mLineVerifier = new LineVerifier(mTestCase, mVCardType);
- }
- return mLineVerifier.addLineVerifierElem();
- }
-
- public ContentValuesVerifierElem addContentValuesVerifierElem() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized");
- }
- if (mContentValuesVerifier == null) {
- mContentValuesVerifier = new ContentValuesVerifier();
- }
-
- return mContentValuesVerifier.addElem(mTestCase);
- }
-
- private void verifyOneVCard(final String vcard) {
- // Log.d("@@@", vcard);
- final VCardInterpreter builder;
- if (mContentValuesVerifier != null) {
- final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier;
- final VCardEntryConstructor vcardDataBuilder =
- new VCardEntryConstructor(mVCardType);
- vcardDataBuilder.addEntryHandler(mContentValuesVerifier);
- if (mPropertyNodesVerifier != null) {
- builder = new VCardInterpreterCollection(Arrays.asList(
- mPropertyNodesVerifier, vcardDataBuilder));
- } else {
- builder = vnodeBuilder;
- }
- } else {
- if (mPropertyNodesVerifier != null) {
- builder = mPropertyNodesVerifier;
- } else {
- return;
- }
- }
-
- final VCardParser parser =
- (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21());
- InputStream is = null;
- try {
- String charset =
- (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8");
- is = new ByteArrayInputStream(vcard.getBytes(charset));
- mTestCase.assertEquals(true, parser.parse(is, null, builder));
- } catch (IOException e) {
- mTestCase.fail("Unexpected IOException: " + e.getMessage());
- } catch (VCardException e) {
- mTestCase.fail("Unexpected VCardException: " + e.getMessage());
- } finally {
- if (is != null) {
- try {
- is.close();
- } catch (IOException e) {
- }
- }
- }
- }
-
- public void verify() {
- if (!mInitialized) {
- mTestCase.fail("Not initialized.");
- }
- if (mVerified) {
- mTestCase.fail("verify() was called twice.");
- }
- if (mInputStream != null) {
- try {
- verifyForImportTest();
- } catch (IOException e) {
- mTestCase.fail("IOException was thrown: " + e.getMessage());
- } catch (VCardException e) {
- mTestCase.fail("VCardException was thrown: " + e.getMessage());
- }
- } else if (mExportTestResolver != null){
- verifyForExportTest();
- } else {
- mTestCase.fail("No input is determined");
- }
- mVerified = true;
- }
-
- private void verifyForImportTest() throws IOException, VCardException {
- if (mLineVerifier != null) {
- mTestCase.fail("Not supported now.");
- }
- if (mContentValuesVerifier != null) {
- mContentValuesVerifier.verify(mInputStream, mVCardType);
- }
- }
-
- public static EntityIterator mockGetEntityIteratorMethod(
- final ContentResolver resolver,
- final Uri uri, final String selection,
- final String[] selectionArgs, final String sortOrder) {
- if (ExportTestResolver.class.equals(resolver.getClass())) {
- return ((ExportTestResolver)resolver).getProvider().queryEntities(
- uri, selection, selectionArgs, sortOrder);
- }
-
- Log.e(LOG_TAG, "Unexpected provider given.");
- return null;
- }
-
- private Method getMockGetEntityIteratorMethod()
- throws SecurityException, NoSuchMethodException {
- return this.getClass().getMethod("mockGetEntityIteratorMethod",
- ContentResolver.class, Uri.class, String.class, String[].class, String.class);
- }
-
- private void verifyForExportTest() {
- final VCardComposer composer =
- new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType);
- composer.addHandler(mLineVerifier);
- composer.addHandler(mVCardVerifierInternal);
- if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
- AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
- }
- AndroidTestCase.assertFalse(composer.isAfterLast());
- try {
- while (!composer.isAfterLast()) {
- try {
- final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
- AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod);
- AndroidTestCase.assertTrue(
- composer.createOneEntry(mockGetEntityIteratorMethod));
- } catch (Exception e) {
- e.printStackTrace();
- AndroidTestCase.fail();
- }
- }
- } finally {
- composer.terminate();
- }
- }
-}
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
new file mode 100644
index 0000000..843750e
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
index b3c0773..bdcccf9 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java
@@ -13,8 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
@@ -22,7 +21,7 @@
* ContentValues-like class which enables users to chain put() methods and restricts
* the other methods.
*/
-/* package */ class ContentValuesBuilder {
+public class ContentValuesBuilder {
private final ContentValues mContentValues;
public ContentValuesBuilder(final ContentValues contentValues) {
@@ -34,6 +33,7 @@
return this;
}
+ /*
public ContentValuesBuilder put(String key, Byte value) {
mContentValues.put(key, value);
return this;
@@ -42,13 +42,14 @@
public ContentValuesBuilder put(String key, Short value) {
mContentValues.put(key, value);
return this;
- }
+ }*/
public ContentValuesBuilder put(String key, Integer value) {
mContentValues.put(key, value);
return this;
}
+ /*
public ContentValuesBuilder put(String key, Long value) {
mContentValues.put(key, value);
return this;
@@ -67,7 +68,7 @@
public ContentValuesBuilder put(String key, Boolean value) {
mContentValues.put(key, value);
return this;
- }
+ }*/
public ContentValuesBuilder put(String key, byte[] value) {
mContentValues.put(key, value);
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
new file mode 100644
index 0000000..d0613c6
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java
@@ -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();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
similarity index 78%
rename from core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
index 2edbb36..03c18e1 100644
--- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java
@@ -13,17 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
-import android.pim.vcard.VCardConfig;
import android.pim.vcard.VCardEntry;
import android.pim.vcard.VCardEntryCommitter;
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.VCardUtils;
import android.pim.vcard.exception.VCardException;
import android.provider.ContactsContract.Data;
import android.test.AndroidTestCase;
@@ -31,7 +29,7 @@
import java.io.IOException;
import java.io.InputStream;
-/* package */ class ContentValuesVerifierElem {
+public class ContentValuesVerifierElem {
private final AndroidTestCase mTestCase;
private final ImportTestResolver mResolver;
private final VCardEntryHandler mHandler;
@@ -49,20 +47,14 @@
return new ContentValuesBuilder(contentValues);
}
- public void verify(int resId, int vCardType)
+ public void verify(int resId, int vcardType)
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 {
- final VCardParser vCardParser;
- if (VCardConfig.isV30(vCardType)) {
- vCardParser = new VCardParser_V30(true); // use StrictParsing
- } else {
- vCardParser = new VCardParser_V21();
- }
- VCardEntryConstructor builder =
- new VCardEntryConstructor(null, null, false, vCardType, null);
+ public void verify(InputStream is, int vcardType) throws IOException, VCardException {
+ final VCardParser vCardParser = VCardUtils.getAppropriateParser(vcardType);
+ final VCardEntryConstructor builder = new VCardEntryConstructor(vcardType, null);
builder.addEntryHandler(mHandler);
try {
vCardParser.parse(is, builder);
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
new file mode 100644
index 0000000..e095258
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java
@@ -0,0 +1,174 @@
+/*
+ * 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.ContentResolver;
+import android.content.ContentValues;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.database.Cursor;
+import android.net.Uri;
+import android.pim.vcard.VCardComposer;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockCursor;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ExportTestProvider extends MockContentProvider {
+ final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>();
+
+ private static 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() {
+ }
+ }
+
+ public ExportTestProvider(AndroidTestCase androidTestCase) {
+ }
+
+ public ContactEntry buildInputEntry() {
+ ContactEntry contactEntry = new ContactEntry();
+ mContactEntryList.add(contactEntry);
+ return contactEntry;
+ }
+
+ /**
+ * <p>
+ * An old method which had existed but was removed from ContentResolver.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ */
+ public EntityIterator queryEntities(Uri uri,
+ String selection, String[] selectionArgs, String sortOrder) {
+ TestCase.assertTrue(uri != null);
+ TestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()));
+ final String authority = uri.getAuthority();
+ TestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority));
+ TestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection));
+ TestCase.assertEquals(1, selectionArgs.length);
+ final int id = Integer.parseInt(selectionArgs[0]);
+ TestCase.assertTrue(id >= 0 && id < mContactEntryList.size());
+
+ return new MockEntityIterator(mContactEntryList.get(id).getList());
+ }
+
+ @Override
+ public Cursor query(Uri uri,String[] projection,
+ String selection, String[] selectionArgs, String sortOrder) {
+ TestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri));
+ // In this test, following arguments are not supported.
+ TestCase.assertNull(selection);
+ TestCase.assertNull(selectionArgs);
+ TestCase.assertNull(sortOrder);
+
+ return new MockCursor() {
+ int mCurrentPosition = -1;
+
+ @Override
+ public int getCount() {
+ return mContactEntryList.size();
+ }
+
+ @Override
+ public boolean moveToFirst() {
+ mCurrentPosition = 0;
+ return true;
+ }
+
+ @Override
+ public boolean moveToNext() {
+ if (mCurrentPosition < mContactEntryList.size()) {
+ mCurrentPosition++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isBeforeFirst() {
+ return mCurrentPosition < 0;
+ }
+
+ @Override
+ public boolean isAfterLast() {
+ return mCurrentPosition >= mContactEntryList.size();
+ }
+
+ @Override
+ public int getColumnIndex(String columnName) {
+ TestCase.assertEquals(Contacts._ID, columnName);
+ return 0;
+ }
+
+ @Override
+ public int getInt(int columnIndex) {
+ TestCase.assertEquals(0, columnIndex);
+ TestCase.assertTrue(mCurrentPosition >= 0
+ && mCurrentPosition < mContactEntryList.size());
+ return mCurrentPosition;
+ }
+
+ @Override
+ public String getString(int columnIndex) {
+ return String.valueOf(getInt(columnIndex));
+ }
+
+ @Override
+ public void close() {
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
new file mode 100644
index 0000000..606d91a
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java
@@ -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;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
similarity index 82%
rename from core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
index 1563da9..df89491 100644
--- a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java
@@ -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 use this file except in compliance with the License.
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
@@ -34,8 +34,8 @@
import android.provider.ContactsContract.CommonDataKinds.Website;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
+import android.test.AndroidTestCase;
import android.test.mock.MockContentProvider;
-import android.test.mock.MockContentResolver;
import android.text.TextUtils;
import junit.framework.TestCase;
@@ -51,38 +51,7 @@
import java.util.SortedMap;
import java.util.TreeMap;
-/* package */ class ImportTestResolver extends MockContentResolver {
- 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 {
+public class ImportTestProvider extends MockContentProvider {
private static final Set<String> sKnownMimeTypeSet =
new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE,
Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE,
@@ -95,10 +64,7 @@
final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues;
- private final TestCase mTestCase;
-
- public ImportTestProvider(TestCase testCase) {
- mTestCase = testCase;
+ public ImportTestProvider(AndroidTestCase androidTestCase) {
mMimeTypeToExpectedContentValues =
new HashMap<String, Collection<ContentValues>>();
for (String acceptanbleMimeType : sKnownMimeTypeSet) {
@@ -113,7 +79,7 @@
public void addExpectedContentValues(ContentValues expectedContentValues) {
final String mimeType = expectedContentValues.getAsString(Data.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.",
mimeType));
}
@@ -127,7 +93,7 @@
public ContentProviderResult[] applyBatch(
ArrayList<ContentProviderOperation> operations) {
if (operations == null) {
- mTestCase.fail("There is no operation.");
+ TestCase.fail("There is no operation.");
}
final int size = operations.size();
@@ -148,12 +114,12 @@
fakeResultArray, i);
final Uri uri = operation.getUri();
if (uri.equals(RawContacts.CONTENT_URI)) {
- mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
- mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
+ TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME));
+ TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE));
} else if (uri.equals(Data.CONTENT_URI)) {
final String mimeType = actualContentValues.getAsString(Data.MIMETYPE);
if (!sKnownMimeTypeSet.contains(mimeType)) {
- mTestCase.fail(String.format(
+ TestCase.fail(String.format(
"Unknown MimeType %s. Probably added after developing this test",
mimeType));
}
@@ -185,7 +151,7 @@
final Collection<ContentValues> contentValuesCollection =
mMimeTypeToExpectedContentValues.get(mimeType);
if (contentValuesCollection.isEmpty()) {
- mTestCase.fail("ContentValues for MimeType " + mimeType
+ TestCase.fail("ContentValues for MimeType " + mimeType
+ " is not expected at all (" + actualContentValues + ")");
}
boolean checked = false;
@@ -197,23 +163,25 @@
+ convertToEasilyReadableString(actualContentValues));*/
if (equalsForContentValues(expectedContentValues,
actualContentValues)) {
- mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
+ TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues));
checked = true;
break;
}
}
if (!checked) {
final StringBuilder builder = new StringBuilder();
+ builder.append("\n");
builder.append("Unexpected: ");
builder.append(convertToEasilyReadableString(actualContentValues));
- builder.append("\nExpected: ");
+ builder.append("\n");
+ builder.append("Expected : ");
for (ContentValues expectedContentValues : contentValuesCollection) {
builder.append(convertToEasilyReadableString(expectedContentValues));
}
- mTestCase.fail(builder.toString());
+ TestCase.fail(builder.toString());
}
} else {
- mTestCase.fail("Unexpected Uri has come: " + uri);
+ TestCase.fail("Unexpected Uri has come: " + uri);
}
} // for (int i = 0; i < size; i++) {
return fakeResultArray;
@@ -232,7 +200,7 @@
final String failMsg =
"There is(are) remaining expected ContentValues instance(s): \n"
+ builder.toString();
- mTestCase.fail(failMsg);
+ TestCase.fail(failMsg);
}
}
@@ -252,7 +220,7 @@
if (Data.MIMETYPE.equals(key)) {
mimeTypeValue = valueString;
} else {
- mTestCase.assertNotNull(key);
+ TestCase.assertNotNull(key);
sortedMap.put(key, valueString);
}
}
@@ -273,7 +241,7 @@
}
private static boolean equalsForContentValues(
- ContentValues expected, ContentValues actual) {
+ final ContentValues expected, final ContentValues actual) {
if (expected == actual) {
return true;
} else if (expected == null || actual == null || expected.size() != actual.size()) {
@@ -286,15 +254,21 @@
if (!actual.containsKey(key)) {
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[]) {
- Object actualValue = actual.get(key);
if (!Arrays.equals((byte[])value, (byte[])actualValue)) {
+ byte[] e = (byte[])value;
+ byte[] a = (byte[])actualValue;
return false;
}
- } else if (!value.equals(actual.get(key))) {
- return false;
+ } else if (!value.equals(actualValue) &&
+ !value.toString().equals(actualValue.toString())) {
+ return false;
}
}
return true;
}
-}
+}
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
new file mode 100644
index 0000000..1822afe
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java
@@ -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);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
similarity index 72%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
index cef15fd7..e314ac5 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java
@@ -13,39 +13,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.Context;
import android.pim.vcard.VCardComposer;
+import android.test.AndroidTestCase;
import junit.framework.TestCase;
import java.util.ArrayList;
-class LineVerifier implements VCardComposer.OneEntryHandler {
- private final TestCase mTestCase;
+public class LineVerifier implements VCardComposer.OneEntryHandler {
+ private final AndroidTestCase mAndroidTestCase;
private final ArrayList<LineVerifierElem> mLineVerifierElemList;
private int mVCardType;
private int index;
- public LineVerifier(TestCase testCase, int vcardType) {
- mTestCase = testCase;
+ public LineVerifier(AndroidTestCase androidTestCase, int vcardType) {
+ mAndroidTestCase = androidTestCase;
mLineVerifierElemList = new ArrayList<LineVerifierElem>();
mVCardType = vcardType;
}
public LineVerifierElem addLineVerifierElem() {
- LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType);
+ LineVerifierElem lineVerifier = new LineVerifierElem(mAndroidTestCase, mVCardType);
mLineVerifierElemList.add(lineVerifier);
return lineVerifier;
}
public void verify(String vcard) {
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);
index++;
diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
similarity index 72%
rename from core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
index b23b29b..e23a9cd 100644
--- a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java
@@ -13,9 +13,10 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.pim.vcard.VCardConfig;
+import android.test.AndroidTestCase;
import android.text.TextUtils;
import junit.framework.TestCase;
@@ -23,14 +24,12 @@
import java.util.ArrayList;
import java.util.List;
-class LineVerifierElem {
- private final TestCase mTestCase;
+public class LineVerifierElem {
private final List<String> mExpectedLineList = new ArrayList<String>();
- private final boolean mIsV30;
+ private final int mVCardType;
- public LineVerifierElem(TestCase testCase, int vcardType) {
- mTestCase = testCase;
- mIsV30 = VCardConfig.isV30(vcardType);
+ public LineVerifierElem(AndroidTestCase androidTestCase, int vcardType) {
+ mVCardType = vcardType;
}
public LineVerifierElem addExpected(final String line) {
@@ -55,21 +54,23 @@
if ("BEGIN:VCARD".equalsIgnoreCase(line)) {
if (beginExists) {
- mTestCase.fail("Multiple \"BEGIN:VCARD\" line found");
+ TestCase.fail("Multiple \"BEGIN:VCARD\" line found");
} else {
beginExists = true;
continue;
}
} else if ("END:VCARD".equalsIgnoreCase(line)) {
if (endExists) {
- mTestCase.fail("Multiple \"END:VCARD\" line found");
+ TestCase.fail("Multiple \"END:VCARD\" line found");
} else {
endExists = true;
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) {
- mTestCase.fail("Multiple VERSION line + found");
+ TestCase.fail("Multiple VERSION line + found");
} else {
versionExists = true;
continue;
@@ -77,18 +78,16 @@
}
if (!beginExists) {
- mTestCase.fail("Property other than BEGIN came before BEGIN property: "
- + line);
+ TestCase.fail("Property other than BEGIN came before BEGIN property: " + line);
} else if (endExists) {
- mTestCase.fail("Property other than END came after END property: "
- + line);
+ TestCase.fail("Property other than END came after END property: " + line);
}
final int index = mExpectedLineList.indexOf(line);
if (index >= 0) {
mExpectedLineList.remove(index);
} else {
- mTestCase.fail("Unexpected line: " + line);
+ TestCase.fail("Unexpected line: " + line);
}
}
@@ -99,7 +98,7 @@
buffer.append("\n");
}
- mTestCase.fail("Expected line(s) not found:" + buffer.toString());
+ TestCase.fail("Expected line(s) not found:" + buffer.toString());
}
}
}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
index 2c1f6d2..de7ad8e 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
import android.pim.vcard.VCardEntry;
-import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
@@ -26,12 +25,18 @@
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.
- *
+ * </p>
+ * <p>
* 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
* 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 String propName;
@@ -43,7 +48,8 @@
*/
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
* defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as
* one String value like "A,B", not ["A", "B"]...
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
new file mode 100644
index 0000000..4229284
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java
@@ -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++;
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
similarity index 77%
rename from core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
index cfdd074..44c6abc 100644
--- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java
@@ -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 use this file except in compliance with the License.
@@ -13,87 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
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 junit.framework.TestCase;
-import java.io.IOException;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
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.
*
@@ -102,7 +34,7 @@
* 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.
*/
-/* package */ class PropertyNodesVerifierElem {
+public class PropertyNodesVerifierElem {
public static class TypeSet extends HashSet<String> {
public TypeSet(String ... array) {
super(Arrays.asList(array));
@@ -119,12 +51,10 @@
// Intentionally use ArrayList instead of Set, assuming there may be more than one
// exactly same objects.
private final ArrayList<PropertyNode> mUnorderedNodeList;
- private final TestCase mTestCase;
- public PropertyNodesVerifierElem(TestCase testCase) {
+ public PropertyNodesVerifierElem(AndroidTestCase androidTestCase) {
mOrderedNodeMap = new HashMap<String, List<PropertyNode>>();
mUnorderedNodeList = new ArrayList<PropertyNode>();
- mTestCase = testCase;
}
// WithOrder
@@ -170,6 +100,12 @@
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,
ContentValues paramMap, TypeSet paramMap_TYPE) {
return addExpectedNodeWithOrder(propName, propValue, null, null,
@@ -183,12 +119,18 @@
}
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,
List<String> propValueList, byte[] propValue_bytes,
ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) {
if (propValue == null && propValueList != null) {
propValue = concatinateListWithSemiColon(propValueList);
}
- PropertyNode propertyNode = new PropertyNode(propName,
+ final PropertyNode propertyNode = new PropertyNode(propName,
propValue, propValueList, propValue_bytes,
paramMap, paramMap_TYPE, propGroupSet);
List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName);
@@ -267,8 +209,9 @@
for (PropertyNode actualNode : vnode.propList) {
verifyNode(actualNode.propName, actualNode);
}
+
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 (PropertyNode node : nodes) {
if (!expectedProps.contains(node.propName)) {
@@ -281,18 +224,19 @@
expectedProps.add(node.propName);
}
}
- mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ TestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray())
+ " was not found.");
}
}
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);
if (size > 0) {
for (int i = 0; i < size; i++) {
- PropertyNode expectedNode = expectedNodeList.get(i);
- List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>();
+ final PropertyNode expectedNode = expectedNodeList.get(i);
+ final List<PropertyNode> expectedButDifferentValueList =
+ new ArrayList<PropertyNode>();
if (expectedNode.propName.equals(propName)) {
if (expectedNode.equals(actualNode)) {
expectedNodeList.remove(i);
@@ -318,7 +262,7 @@
expectedButDifferentValueList);
} else {
// There's no expected node with same propName.
- mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ TestCase.fail("Unexpected property \"" + propName + "\" exists.");
}
}
} else {
@@ -333,7 +277,7 @@
expectedButDifferentValueList);
} else {
// There's no expected node with same propName.
- mTestCase.fail("Unexpected property \"" + propName + "\" exists.");
+ TestCase.fail("Unexpected property \"" + propName + "\" exists.");
}
}
}
@@ -379,7 +323,7 @@
builder.append(expectedNode.toString());
builder.append("\n");
}
- mTestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ TestCase.fail("Property \"" + propName + "\" has wrong value.\n"
+ builder.toString()
+ " actual: " + actualNode.toString());
}
diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java
similarity index 92%
rename from core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java
index 9c6003f..e7413ab 100644
--- a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
+import android.pim.vcard.VCardConfig;
import android.test.AndroidTestCase;
/**
@@ -24,8 +24,9 @@
* Please do not add each unit test here.
*/
public class VCardTestsBase extends AndroidTestCase {
- public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8;
- public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8;
+ public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC;
+ 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.
protected final ContentValues mContentValuesForQP;
@@ -41,6 +42,7 @@
public VCardTestsBase() {
super();
+ // Not using constants in vCard code since it may be wrong.
mContentValuesForQP = new ContentValues();
mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE");
mContentValuesForSJis = new ContentValues();
diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
new file mode 100644
index 0000000..7379a5b
--- /dev/null
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java
@@ -0,0 +1,370 @@
+/*
+ * 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.ContentResolver;
+import android.content.Context;
+import android.content.EntityIterator;
+import android.net.Uri;
+import android.pim.vcard.VCardComposer;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntryConstructor;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardInterpreterCollection;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardUtils;
+import android.pim.vcard.exception.VCardException;
+import android.test.AndroidTestCase;
+import android.test.mock.MockContext;
+import android.text.TextUtils;
+import android.util.Log;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * <p>
+ * The class lets users checks that given expected vCard data are same as given actual vCard data.
+ * Able to verify both vCard importer/exporter.
+ * </p>
+ * <p>
+ * First a user has to initialize the object by calling either
+ * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}.
+ * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported.
+ * </p>
+ */
+public class 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 {
+ public boolean onInit(Context context) {
+ return true;
+ }
+ public boolean onEntryCreated(String vcard) {
+ verifyOneVCardForExport(vcard);
+ return true;
+ }
+ public void onTerminate() {
+ }
+ }
+
+ private final AndroidTestCase mAndroidTestCase;
+ private final VCardVerifierInternal mVCardVerifierInternal;
+ private int mVCardType;
+ private boolean mIsDoCoMo;
+
+ // Only one of them must be non-empty.
+ private ExportTestResolver mExportTestResolver;
+ private InputStream mInputStream;
+
+ // To allow duplication, use list instead of set.
+ // When null, we don't need to do the verification.
+ private PropertyNodesVerifier mPropertyNodesVerifier;
+ private LineVerifier mLineVerifier;
+ private ContentValuesVerifier mContentValuesVerifier;
+ private boolean mInitialized;
+ private boolean mVerified = false;
+ private String mCharset;
+
+ // Called by VCardTestsBase
+ public VCardVerifier(AndroidTestCase androidTestCase) {
+ mAndroidTestCase = androidTestCase;
+ mVCardVerifierInternal = new VCardVerifierInternal();
+ mExportTestResolver = null;
+ mInputStream = null;
+ mInitialized = false;
+ mVerified = false;
+ }
+
+ // Should be called at the beginning of each import test.
+ public void initForImportTest(int vcardType, int resId) {
+ if (mInitialized) {
+ AndroidTestCase.fail("Already initialized");
+ }
+ mVCardType = vcardType;
+ mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);
+ setInputResourceId(resId);
+ 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) {
+ InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId);
+ if (inputStream == null) {
+ AndroidTestCase.fail("Wrong resId: " + resId);
+ }
+ setInputStream(inputStream);
+ }
+
+ private void setInputStream(InputStream inputStream) {
+ if (mExportTestResolver != null) {
+ AndroidTestCase.fail("addInputEntry() is called.");
+ } else if (mInputStream != null) {
+ AndroidTestCase.fail("InputStream is already set");
+ }
+ mInputStream = inputStream;
+ }
+
+ public ContactEntry addInputEntry() {
+ if (!mInitialized) {
+ AndroidTestCase.fail("Not initialized");
+ }
+ if (mInputStream != null) {
+ AndroidTestCase.fail("setInputStream is called");
+ }
+ return mExportTestResolver.addInputContactEntry();
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() {
+ if (!mInitialized) {
+ AndroidTestCase.fail("Not initialized");
+ }
+ if (mPropertyNodesVerifier == null) {
+ mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase);
+ }
+ return mPropertyNodesVerifier.addPropertyNodesVerifierElem();
+ }
+
+ 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;
+ }
+
+ public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() {
+ if (!mInitialized) {
+ AndroidTestCase.fail("Not initialized");
+ }
+ final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem();
+ if (VCardConfig.isVersion40(mVCardType)) {
+ elem.addExpectedNodeWithOrder("FN", "");
+ } else if (VCardConfig.isVersion30(mVCardType)) {
+ elem.addExpectedNodeWithOrder("N", "");
+ elem.addExpectedNodeWithOrder("FN", "");
+ } else if (mIsDoCoMo) {
+ elem.addExpectedNodeWithOrder("N", "");
+ }
+ return elem;
+ }
+
+ public LineVerifierElem addLineVerifierElem() {
+ if (!mInitialized) {
+ AndroidTestCase.fail("Not initialized");
+ }
+ if (mLineVerifier == null) {
+ mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType);
+ }
+ return mLineVerifier.addLineVerifierElem();
+ }
+
+ public ContentValuesVerifierElem addContentValuesVerifierElem() {
+ if (!mInitialized) {
+ AndroidTestCase.fail("Not initialized");
+ }
+ if (mContentValuesVerifier == null) {
+ mContentValuesVerifier = new ContentValuesVerifier();
+ }
+
+ return mContentValuesVerifier.addElem(mAndroidTestCase);
+ }
+
+ /**
+ * Sets up sub-verifiers correctly and try parse given vCard as InputStream.
+ * 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) {
+ final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType);
+ constructor.addEntryHandler(mContentValuesVerifier);
+ if (mPropertyNodesVerifier != null) {
+ interpreter = new VCardInterpreterCollection(Arrays.asList(
+ mPropertyNodesVerifier, constructor));
+ } else {
+ interpreter = constructor;
+ }
+ } else {
+ if (mPropertyNodesVerifier != null) {
+ interpreter = mPropertyNodesVerifier;
+ } else {
+ interpreter = null;
+ }
+ }
+
+ try {
+ // 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;
+ try {
+ is = new ByteArrayInputStream(vcard.getBytes(mCharset));
+ verifyWithInputStream(is);
+ } catch (IOException e) {
+ AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ AndroidTestCase.fail("Unexpected IOException: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ public void verify() {
+ if (!mInitialized) {
+ TestCase.fail("Not initialized.");
+ }
+ if (mVerified) {
+ TestCase.fail("verify() was called twice.");
+ }
+
+ if (mInputStream != null) {
+ if (mExportTestResolver != null){
+ TestCase.fail("There are two input sources.");
+ }
+ verifyForImportTest();
+ } else if (mExportTestResolver != null){
+ verifyForExportTest();
+ } else {
+ TestCase.fail("No input is determined");
+ }
+ mVerified = true;
+ }
+
+ private void verifyForImportTest() {
+ if (mLineVerifier != null) {
+ AndroidTestCase.fail("Not supported now.");
+ }
+
+ try {
+ verifyWithInputStream(mInputStream);
+ } catch (IOException e) {
+ AndroidTestCase.fail("IOException was thrown: " + e.getMessage());
+ } finally {
+ if (mInputStream != null) {
+ try {
+ mInputStream.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ }
+
+ public static EntityIterator mockGetEntityIteratorMethod(
+ final ContentResolver resolver,
+ final Uri uri, final String selection,
+ final String[] selectionArgs, final String sortOrder) {
+ if (ExportTestResolver.class.equals(resolver.getClass())) {
+ return ((ExportTestResolver)resolver).getProvider().queryEntities(
+ uri, selection, selectionArgs, sortOrder);
+ }
+
+ Log.e(LOG_TAG, "Unexpected provider given.");
+ return null;
+ }
+
+ private Method getMockGetEntityIteratorMethod()
+ throws SecurityException, NoSuchMethodException {
+ return this.getClass().getMethod("mockGetEntityIteratorMethod",
+ ContentResolver.class, Uri.class, String.class, String[].class, String.class);
+ }
+
+ private void verifyForExportTest() {
+ final VCardComposer composer =
+ new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset);
+ composer.addHandler(mLineVerifier);
+ composer.addHandler(mVCardVerifierInternal);
+ if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) {
+ AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason());
+ }
+ AndroidTestCase.assertFalse(composer.isAfterLast());
+ try {
+ while (!composer.isAfterLast()) {
+ try {
+ final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod();
+ AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod);
+ AndroidTestCase.assertTrue(
+ composer.createOneEntry(mockGetEntityIteratorMethod));
+ } catch (Exception e) {
+ e.printStackTrace();
+ AndroidTestCase.fail(e.toString());
+ }
+ }
+ } finally {
+ composer.terminate();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
similarity index 95%
rename from core/tests/coretests/src/android/pim/vcard/VNode.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
index 79f10dc..b890e2c 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNode.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import java.util.ArrayList;
diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
similarity index 62%
rename from core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
rename to core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
index 0e6c325..8837034 100644
--- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java
+++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java
@@ -13,18 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.pim.vcard;
+package android.pim.vcard.test_utils;
import android.content.ContentValues;
-import android.pim.vcard.VCardInterpreter;
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.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;
@@ -32,34 +30,29 @@
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.
- * VNode: standy by a vcard instance.
- * PropertyNode: standy by a property line of a card.
- *
- * Previously used in main vCard handling code but now exists only for testing.
+ * </p>
+ * <p>
+ * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal).
+ * </p>
*/
public class VNodeBuilder implements VCardInterpreter {
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>();
private int mNodeListPos = 0;
private VNode mCurrentVNode;
private PropertyNode mCurrentPropNode;
private String mCurrentParamType;
-
+
/**
* The charset using which VParser parses the text.
*/
private String mSourceCharset;
-
+
/**
* The charset with which byte array is encoded to String.
*/
@@ -68,27 +61,15 @@
private boolean mStrictLineBreakParsing;
public VNodeBuilder() {
- this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false);
+ this(VCardConfig.DEFAULT_IMPORT_CHARSET, false);
}
- public VNodeBuilder(String charset, boolean strictLineBreakParsing) {
- this(null, charset, strictLineBreakParsing);
- }
-
- /**
- * @hide sourceCharset is temporal.
- */
- public VNodeBuilder(String sourceCharset, String targetCharset,
- boolean strictLineBreakParsing) {
- if (sourceCharset != null) {
- mSourceCharset = sourceCharset;
- } else {
- mSourceCharset = VCardConfig.DEFAULT_CHARSET;
- }
+ public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) {
+ mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET;
if (targetCharset != null) {
mTargetCharset = targetCharset;
} else {
- mTargetCharset = TARGET_CHARSET;
+ mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
}
mStrictLineBreakParsing = strictLineBreakParsing;
}
@@ -149,7 +130,6 @@
mCurrentPropNode.propName = name;
}
- // Used only in VCard.
public void propertyGroup(String group) {
mCurrentPropNode.propGroupSet.add(group);
}
@@ -159,6 +139,12 @@
}
public void propertyParamValue(String value) {
+ if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) {
+ value = VCardUtils.convertStringCharset(value,
+ VCardConfig.DEFAULT_INTERMEDIATE_CHARSET,
+ VCardConfig.DEFAULT_IMPORT_CHARSET);
+ }
+
if (mCurrentParamType == null ||
mCurrentParamType.equalsIgnoreCase("TYPE")) {
mCurrentPropNode.paramMap_TYPE.add(value);
@@ -192,71 +178,11 @@
encoding = encoding.toUpperCase();
if (encoding.equals("BASE64") || encoding.equals("B")) {
// Assume BASE64 is used only when the number of values is 1.
- mCurrentPropNode.propValue_bytes =
- Base64.decodeBase64(value.getBytes());
+ mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP);
return value;
} else if (encoding.equals("QUOTED-PRINTABLE")) {
- String quotedPrintable = value
- .replaceAll("= ", " ").replaceAll("=\t", "\t");
- 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);
- }
+ return VCardUtils.parseQuotedPrintable(
+ value, mStrictLineBreakParsing, mSourceCharset, targetCharset);
}
// Unknown encoding. Fall back to default.
}
@@ -309,6 +235,6 @@
}
public String getResult(){
- return null;
+ throw new RuntimeException("Not supported");
}
}
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 7322e6c..6f8afff 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -90,8 +90,6 @@
<assign-permission name="android.permission.CALL_PHONE" uid="shell" />
<assign-permission name="android.permission.READ_CONTACTS" uid="shell" />
<assign-permission name="android.permission.WRITE_CONTACTS" uid="shell" />
- <assign-permission name="android.permission.READ_OWNER_DATA" uid="shell" />
- <assign-permission name="android.permission.WRITE_OWNER_DATA" uid="shell" />
<assign-permission name="android.permission.READ_CALENDAR" uid="shell" />
<assign-permission name="android.permission.WRITE_CALENDAR" uid="shell" />
<assign-permission name="android.permission.READ_USER_DICTIONARY" uid="shell" />
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index a1401ad..8345cc3 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -1934,8 +1934,8 @@
ssize_t offset = getEntry(package, t, e, &mParams, &type, &entry, &typeClass);
if (offset <= 0) {
if (offset < 0) {
- LOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %d: 0x%08x\n",
- resID, t, e, (int)ip, (int)offset);
+ LOGW("Failure getting entry for 0x%08x (t=%d e=%d) in package %zd (error %d)\n",
+ resID, t, e, ip, (int)offset);
return offset;
}
continue;
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 1acdaaf..3770b55 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -5992,12 +5992,14 @@
p,
&rsize,
&reply);
- if (ret == NO_ERROR) {
- if (reply != NO_ERROR) {
- status = reply;
- }
- } else {
+ // stop at first error encountered
+ if (ret != NO_ERROR) {
status = ret;
+ *(int *)pReplyData = reply;
+ break;
+ } else if (reply != NO_ERROR) {
+ *(int *)pReplyData = reply;
+ break;
}
mCblk->serverIndex += size;
}
@@ -6005,8 +6007,10 @@
mCblk->clientIndex = 0;
return status;
} else if (cmdCode == EFFECT_CMD_ENABLE) {
+ *(int *)pReplyData = NO_ERROR;
return enable();
} else if (cmdCode == EFFECT_CMD_DISABLE) {
+ *(int *)pReplyData = NO_ERROR;
return disable();
}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index c21a221..5f79b85 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -2885,7 +2885,8 @@
+ " res=" + resultCode + " data=" + resultData);
if (r.info.applicationInfo.uid > 0) {
mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
- r.packageName, resultData, r.getUriPermissionsLocked());
+ resultTo.packageName, resultData,
+ resultTo.getUriPermissionsLocked());
}
resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode,
resultData);
diff --git a/services/java/com/android/server/sip/SipHelper.java b/services/java/com/android/server/sip/SipHelper.java
index 83eeb84..d9a1bbf 100644
--- a/services/java/com/android/server/sip/SipHelper.java
+++ b/services/java/com/android/server/sip/SipHelper.java
@@ -20,7 +20,6 @@
import gov.nist.javax.sip.clientauthutils.AccountManager;
import gov.nist.javax.sip.clientauthutils.AuthenticationHelper;
-import android.net.sip.SessionDescription;
import android.net.sip.SipProfile;
import android.util.Log;
@@ -243,7 +242,7 @@
}
public ClientTransaction sendInvite(SipProfile caller, SipProfile callee,
- SessionDescription sessionDescription, String tag)
+ String sessionDescription, String tag)
throws SipException {
try {
FromHeader fromHeader = createFromHeader(caller, tag);
@@ -259,9 +258,9 @@
toHeader, viaHeaders, maxForwards);
request.addHeader(createContactHeader(caller));
- request.setContent(sessionDescription.getContent(),
+ request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
- "application", sessionDescription.getType()));
+ "application", "sdp"));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
@@ -273,12 +272,12 @@
}
public ClientTransaction sendReinvite(Dialog dialog,
- SessionDescription sessionDescription) throws SipException {
+ String sessionDescription) throws SipException {
try {
Request request = dialog.createRequest(Request.INVITE);
- request.setContent(sessionDescription.getContent(),
+ request.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
- "application", sessionDescription.getType()));
+ "application", "sdp"));
ClientTransaction clientTransaction =
mSipProvider.getNewClientTransaction(request);
@@ -326,7 +325,7 @@
* @param event the INVITE request event
*/
public ServerTransaction sendInviteOk(RequestEvent event,
- SipProfile localProfile, SessionDescription sessionDescription,
+ SipProfile localProfile, String sessionDescription,
ServerTransaction inviteTransaction)
throws SipException {
try {
@@ -334,9 +333,9 @@
Response response = mMessageFactory.createResponse(Response.OK,
request);
response.addHeader(createContactHeader(localProfile));
- response.setContent(sessionDescription.getContent(),
+ response.setContent(sessionDescription,
mHeaderFactory.createContentTypeHeader(
- "application", sessionDescription.getType()));
+ "application", "sdp"));
if (inviteTransaction == null) {
inviteTransaction = getServerTransaction(event);
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index 626b488..563ce58 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -53,6 +53,9 @@
import java.util.TreeSet;
import javax.sip.SipException;
+/**
+ * @hide
+ */
public final class SipService extends ISipService.Stub {
private static final String TAG = "SipService";
private static final int EXPIRY_TIME = 3600;
@@ -442,7 +445,7 @@
@Override
public void onRinging(ISipSession session, SipProfile caller,
- byte[] sessionDescription) {
+ String sessionDescription) {
synchronized (SipService.this) {
try {
if (!isRegistered()) {
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index d33558b..8019bfa 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -20,6 +20,7 @@
import gov.nist.javax.sip.clientauthutils.UserCredentials;
import gov.nist.javax.sip.header.SIPHeaderNames;
import gov.nist.javax.sip.header.WWWAuthenticate;
+import gov.nist.javax.sip.message.SIPMessage;
import android.net.sip.ISipSession;
import android.net.sip.ISipSessionListener;
@@ -31,6 +32,7 @@
import android.util.Log;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
import java.net.DatagramSocket;
import java.text.ParseException;
import java.util.Collection;
@@ -284,6 +286,22 @@
}
}
+ private String extractContent(Message message) {
+ // Currently we do not support secure MIME bodies.
+ byte[] bytes = message.getRawContent();
+ if (bytes != null) {
+ try {
+ if (message instanceof SIPMessage) {
+ return ((SIPMessage) message).getMessageContent();
+ } else {
+ return new String(bytes, "UTF-8");
+ }
+ } catch (UnsupportedEncodingException e) {
+ }
+ }
+ return null;
+ }
+
private class SipSessionCallReceiverImpl extends SipSessionImpl {
public SipSessionCallReceiverImpl(ISipSessionListener listener) {
super(listener);
@@ -302,7 +320,7 @@
newSession.mPeerProfile = createPeerProfile(event.getRequest());
newSession.mState = SipSessionState.INCOMING_CALL;
newSession.mPeerSessionDescription =
- event.getRequest().getRawContent();
+ extractContent(event.getRequest());
addSipSession(newSession);
mProxy.onRinging(newSession, newSession.mPeerProfile,
newSession.mPeerSessionDescription);
@@ -321,7 +339,7 @@
Dialog mDialog;
ServerTransaction mServerTransaction;
ClientTransaction mClientTransaction;
- byte[] mPeerSessionDescription;
+ String mPeerSessionDescription;
boolean mInCall;
boolean mReRegisterFlag = false;
@@ -401,12 +419,12 @@
}
public void makeCall(SipProfile peerProfile,
- SessionDescription sessionDescription) {
+ String sessionDescription) {
doCommandAsync(
new MakeCallCommand(peerProfile, sessionDescription));
}
- public void answerCall(SessionDescription sessionDescription) {
+ public void answerCall(String sessionDescription) {
try {
processCommand(
new MakeCallCommand(mPeerProfile, sessionDescription));
@@ -419,7 +437,7 @@
doCommandAsync(END_CALL);
}
- public void changeCall(SessionDescription sessionDescription) {
+ public void changeCall(String sessionDescription) {
doCommandAsync(
new MakeCallCommand(mPeerProfile, sessionDescription));
}
@@ -726,10 +744,9 @@
if (evt instanceof MakeCallCommand) {
MakeCallCommand cmd = (MakeCallCommand) evt;
mPeerProfile = cmd.getPeerProfile();
- SessionDescription sessionDescription =
- cmd.getSessionDescription();
mClientTransaction = mSipHelper.sendInvite(mLocalProfile,
- mPeerProfile, sessionDescription, generateTag());
+ mPeerProfile, cmd.getSessionDescription(),
+ generateTag());
mDialog = mClientTransaction.getDialog();
addSipSession(this);
mState = SipSessionState.OUTGOING_CALL;
@@ -811,7 +828,7 @@
return true;
case Response.OK:
mSipHelper.sendInviteAck(event, mDialog);
- mPeerSessionDescription = response.getRawContent();
+ mPeerSessionDescription = extractContent(response);
establishCall();
return true;
case Response.PROXY_AUTHENTICATION_REQUIRED:
@@ -897,7 +914,7 @@
// got Re-INVITE
RequestEvent event = mInviteReceived = (RequestEvent) evt;
mState = SipSessionState.INCOMING_CALL;
- mPeerSessionDescription = event.getRequest().getRawContent();
+ mPeerSessionDescription = extractContent(event.getRequest());
mServerTransaction = null;
mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
return true;
@@ -1060,10 +1077,10 @@
}
private class MakeCallCommand extends EventObject {
- private SessionDescription mSessionDescription;
+ private String mSessionDescription;
public MakeCallCommand(SipProfile peerProfile,
- SessionDescription sessionDescription) {
+ String sessionDescription) {
super(peerProfile);
mSessionDescription = sessionDescription;
}
@@ -1072,7 +1089,7 @@
return (SipProfile) getSource();
}
- public SessionDescription getSessionDescription() {
+ public String getSessionDescription() {
return mSessionDescription;
}
}
diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java
index fd49fd8..7004204 100644
--- a/services/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -56,7 +56,7 @@
}
public void onRinging(final ISipSession session, final SipProfile caller,
- final byte[] sessionDescription) {
+ final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
@@ -83,7 +83,7 @@
}
public void onCallEstablished(final ISipSession session,
- final byte[] sessionDescription) {
+ final String sessionDescription) {
if (mListener == null) return;
proxy(new Runnable() {
public void run() {
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index b0f086b..f71ebb9 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -853,6 +853,15 @@
error.string());
goto bail;
}
+ } else if (tag == "uses-package") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (name != "" && error == "") {
+ printf("uses-package:'%s'\n", name.string());
+ } else {
+ fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n",
+ error.string());
+ goto bail;
+ }
} else if (tag == "original-package") {
String8 name = getAttribute(tree, NAME_ATTR, &error);
if (name != "" && error == "") {
diff --git a/voip/java/android/net/sip/ISipSession.aidl b/voip/java/android/net/sip/ISipSession.aidl
index fbcb056..1a23527 100644
--- a/voip/java/android/net/sip/ISipSession.aidl
+++ b/voip/java/android/net/sip/ISipSession.aidl
@@ -115,8 +115,7 @@
* @param sessionDescription the session description of this call
* @see ISipSessionListener
*/
- void makeCall(in SipProfile callee,
- in SessionDescription sessionDescription);
+ void makeCall(in SipProfile callee, String sessionDescription);
/**
* Answers an incoming call with the specified session description. The
@@ -125,7 +124,7 @@
*
* @param sessionDescription the session description to answer this call
*/
- void answerCall(in SessionDescription sessionDescription);
+ void answerCall(String sessionDescription);
/**
* Ends an established call, terminates an outgoing call or rejects an
@@ -143,5 +142,5 @@
*
* @param sessionDescription the new session description
*/
- void changeCall(in SessionDescription sessionDescription);
+ void changeCall(String sessionDescription);
}
diff --git a/voip/java/android/net/sip/ISipSessionListener.aidl b/voip/java/android/net/sip/ISipSessionListener.aidl
index 8570958..c552a57 100644
--- a/voip/java/android/net/sip/ISipSessionListener.aidl
+++ b/voip/java/android/net/sip/ISipSessionListener.aidl
@@ -39,7 +39,7 @@
* @param sessionDescription the caller's session description
*/
void onRinging(in ISipSession session, in SipProfile caller,
- in byte[] sessionDescription);
+ String sessionDescription);
/**
* Called when a RINGING response is received for the INVITE request sent
@@ -55,7 +55,7 @@
* @param sessionDescription the peer's session description
*/
void onCallEstablished(in ISipSession session,
- in byte[] sessionDescription);
+ String sessionDescription);
/**
* Called when the session is terminated.
diff --git a/voip/java/android/net/sip/SdpSessionDescription.java b/voip/java/android/net/sip/SdpSessionDescription.java
index 0c29935..f6ae837 100644
--- a/voip/java/android/net/sip/SdpSessionDescription.java
+++ b/voip/java/android/net/sip/SdpSessionDescription.java
@@ -186,8 +186,8 @@
}
}
- public SdpSessionDescription build() {
- return mSdp;
+ public String build() {
+ return mSdp.toString();
}
}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 3cdd114..8254543 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -168,9 +168,9 @@
* Attaches an incoming call to this call object.
*
* @param session the session that receives the incoming call
- * @param sdp the session description of the incoming call
+ * @param sessionDescription the session description of the incoming call
*/
- void attachCall(ISipSession session, SdpSessionDescription sdp)
+ void attachCall(ISipSession session, String sessionDescription)
throws SipException;
/** Ends a call. */
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
index 5789cd4..a312f83 100644
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ b/voip/java/android/net/sip/SipAudioCallImpl.java
@@ -28,14 +28,6 @@
import android.net.rtp.AudioGroup;
import android.net.rtp.AudioStream;
import android.net.rtp.RtpStream;
-import android.net.sip.ISipSession;
-import android.net.sip.SdpSessionDescription;
-import android.net.sip.SessionDescription;
-import android.net.sip.SipAudioCall;
-import android.net.sip.SipManager;
-import android.net.sip.SipProfile;
-import android.net.sip.SipSessionAdapter;
-import android.net.sip.SipSessionState;
import android.net.wifi.WifiManager;
import android.os.Message;
import android.os.RemoteException;
@@ -200,7 +192,7 @@
@Override
public synchronized void onRinging(ISipSession session,
- SipProfile peerProfile, byte[] sessionDescription) {
+ SipProfile peerProfile, String sessionDescription) {
try {
if ((mSipSession == null) || !mInCall
|| !session.getCallId().equals(mSipSession.getCallId())) {
@@ -222,7 +214,7 @@
}
}
- private synchronized void establishCall(byte[] sessionDescription) {
+ private synchronized void establishCall(String sessionDescription) {
stopRingbackTone();
stopRinging();
try {
@@ -238,7 +230,7 @@
@Override
public void onCallEstablished(ISipSession session,
- byte[] sessionDescription) {
+ String sessionDescription) {
establishCall(sessionDescription);
Listener listener = mListener;
if (listener != null) {
@@ -316,10 +308,10 @@
}
public synchronized void attachCall(ISipSession session,
- SdpSessionDescription sdp) throws SipException {
+ String sessionDescription) throws SipException {
mSipSession = session;
- mPeerSd = sdp;
try {
+ mPeerSd = new SdpSessionDescription(sessionDescription);
session.setListener(this);
} catch (Throwable e) {
Log.e(TAG, "attachCall()", e);
@@ -394,12 +386,12 @@
if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
}
- private SessionDescription createOfferSessionDescription() {
+ private String createOfferSessionDescription() {
AudioCodec[] codecs = AudioCodec.getSystemSupportedCodecs();
return createSdpBuilder(true, convert(codecs)).build();
}
- private SessionDescription createAnswerSessionDescription() {
+ private String createAnswerSessionDescription() {
try {
// choose an acceptable media from mPeerSd to answer
SdpSessionDescription.AudioCodec codec = getCodec(mPeerSd);
@@ -416,7 +408,7 @@
}
}
- private SessionDescription createHoldSessionDescription() {
+ private String createHoldSessionDescription() {
try {
return createSdpBuilder(false, mCodec)
.addMediaAttribute(AUDIO, "sendonly", (String) null)
@@ -448,7 +440,7 @@
return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
}
- private SessionDescription createContinueSessionDescription() {
+ private String createContinueSessionDescription() {
return createSdpBuilder(true, mCodec).build();
}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 40792b9..700fb4e 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -298,21 +298,19 @@
throw new SipException("Call ID missing in incoming call intent");
}
- byte[] offerSd = getOfferSessionDescription(incomingCallIntent);
+ String offerSd = getOfferSessionDescription(incomingCallIntent);
if (offerSd == null) {
throw new SipException("Session description missing in incoming "
+ "call intent");
}
try {
- SdpSessionDescription sdp = new SdpSessionDescription(offerSd);
-
ISipSession session = mSipService.getPendingSession(callId);
if (session == null) return null;
SipAudioCall call = new SipAudioCallImpl(
context, session.getLocalProfile());
call.setRingtoneEnabled(ringtoneEnabled);
- call.attachCall(session, sdp);
+ call.attachCall(session, offerSd);
call.setListener(listener);
return call;
} catch (Throwable t) {
@@ -329,7 +327,7 @@
public static boolean isIncomingCallIntent(Intent intent) {
if (intent == null) return false;
String callId = getCallId(intent);
- byte[] offerSd = getOfferSessionDescription(intent);
+ String offerSd = getOfferSessionDescription(intent);
return ((callId != null) && (offerSd != null));
}
@@ -351,8 +349,8 @@
* @return the offer session description or null if the intent does not
* have it
*/
- public static byte[] getOfferSessionDescription(Intent incomingCallIntent) {
- return incomingCallIntent.getByteArrayExtra(OFFER_SD_KEY);
+ public static String getOfferSessionDescription(Intent incomingCallIntent) {
+ return incomingCallIntent.getStringExtra(OFFER_SD_KEY);
}
/**
@@ -365,7 +363,7 @@
* @hide
*/
public static Intent createIncomingCallBroadcast(String action,
- String callId, byte[] sessionDescription) {
+ String callId, String sessionDescription) {
Intent intent = new Intent(action);
intent.putExtra(CALL_ID_KEY, callId);
intent.putExtra(OFFER_SD_KEY, sessionDescription);
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index 6c99141..aa2da75 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -46,8 +46,7 @@
private String mProfileName;
private boolean mSendKeepAlive = false;
private boolean mAutoRegistration = true;
- private boolean mAllowOutgoingCall = false;
- private int mCallingUid = -1;
+ private transient int mCallingUid = 0;
/** @hide */
public static final Parcelable.Creator<SipProfile> CREATOR =
@@ -245,18 +244,6 @@
}
/**
- * Sets the allow-outgoing-call flag.
- *
- * @param flag true if allowing to make outgoing call on the profile;
- * false otherwise
- * @return this builder object
- */
- public Builder setOutgoingCallAllowed(boolean flag) {
- mProfile.mAllowOutgoingCall = flag;
- return this;
- }
-
- /**
* Builds and returns the SIP profile object.
*
* @return the profile object created
@@ -293,7 +280,6 @@
mProfileName = in.readString();
mSendKeepAlive = (in.readInt() == 0) ? false : true;
mAutoRegistration = (in.readInt() == 0) ? false : true;
- mAllowOutgoingCall = (in.readInt() == 0) ? false : true;
mCallingUid = in.readInt();
}
@@ -307,7 +293,6 @@
out.writeString(mProfileName);
out.writeInt(mSendKeepAlive ? 1 : 0);
out.writeInt(mAutoRegistration ? 1 : 0);
- out.writeInt(mAllowOutgoingCall ? 1 : 0);
out.writeInt(mCallingUid);
}
@@ -435,13 +420,6 @@
}
/**
- * Returns true if allowing to make outgoing calls on this profile.
- */
- public boolean isOutgoingCallAllowed() {
- return mAllowOutgoingCall;
- }
-
- /**
* Sets the calling process's Uid in the sip service.
* @hide
*/
diff --git a/voip/java/android/net/sip/SipSessionAdapter.java b/voip/java/android/net/sip/SipSessionAdapter.java
index cfb71d7..770d4eb 100644
--- a/voip/java/android/net/sip/SipSessionAdapter.java
+++ b/voip/java/android/net/sip/SipSessionAdapter.java
@@ -26,14 +26,14 @@
}
public void onRinging(ISipSession session, SipProfile caller,
- byte[] sessionDescription) {
+ String sessionDescription) {
}
public void onRingingBack(ISipSession session) {
}
- public void onCallEstablished(
- ISipSession session, byte[] sessionDescription) {
+ public void onCallEstablished(ISipSession session,
+ String sessionDescription) {
}
public void onCallEnded(ISipSession session) {