diff options
| author | Daisuke Miyakawa <dmiyakawa@google.com> | 2010-04-06 08:50:13 +0900 |
|---|---|---|
| committer | Daisuke Miyakawa <dmiyakawa@google.com> | 2010-04-06 13:41:27 +0900 |
| commit | 82b8b686521323948cd76946e9bd6d6be019797d (patch) | |
| tree | 8dbdf9be9a20dd7ca1fff53c8e7a0585713f79fb /core/java/android/pim | |
| parent | 1021da0af5f975e1ddd0ae4580b3e03e69af8993 (diff) | |
| download | frameworks_base-82b8b686521323948cd76946e9bd6d6be019797d.zip frameworks_base-82b8b686521323948cd76946e9bd6d6be019797d.tar.gz frameworks_base-82b8b686521323948cd76946e9bd6d6be019797d.tar.bz2 | |
Let vCard importer/exporter select charset.
There are some other bug where charset for import/export is used during vCard handling.
Bug: 2572064
Change-Id: I0aef8a6eba0a7e9263e47368a43dbba05efa91b0
Diffstat (limited to 'core/java/android/pim')
| -rw-r--r-- | core/java/android/pim/vcard/VCardBuilder.java | 220 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardComposer.java | 222 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardConfig.java | 68 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardEntryConstructor.java | 2 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardParser.java | 38 | ||||
| -rw-r--r-- | core/java/android/pim/vcard/VCardParser_V21.java | 58 |
6 files changed, 384 insertions, 224 deletions
diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 0a6415d..c2b342f 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -47,7 +47,22 @@ import java.util.Map; 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)); </pre> */ public class VCardBuilder { private static final String LOG_TAG = "VCardBuilder"; @@ -81,7 +96,6 @@ public class VCardBuilder { private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b"; private static final String SHIFT_JIS = "SHIFT_JIS"; - private static final String UTF_8 = "UTF-8"; private final int mVCardType; @@ -92,21 +106,28 @@ public class VCardBuilder { 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. + */ + public VCardBuilder(final int vcardType, String charset) { mVCardType = vcardType; mIsV30 = VCardConfig.isV30(vcardType); @@ -116,40 +137,77 @@ public class VCardBuilder { 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); - - 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; - } - 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; + final boolean shouldUseUtf8 = VCardConfig.shouldUseUtf8ForExport(vcardType); + final boolean shouldUseShiftJis = VCardConfig.shouldUseShiftJisForExport(vcardType); + + // 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 = !(mIsV30 && shouldUseUtf8); + + if (VCardConfig.isDoCoMo(vcardType) || shouldUseShiftJis) { + 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; + } 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(); } @@ -379,8 +437,8 @@ public class VCardBuilder { 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); @@ -454,18 +512,18 @@ public class VCardBuilder { 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,10 +577,10 @@ public class VCardBuilder { 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); } @@ -549,7 +607,7 @@ public class VCardBuilder { 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 +630,7 @@ public class VCardBuilder { 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,7 +653,7 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedPhoneticFamilyName); mBuilder.append(VCARD_END_OF_LINE); - } + } // if (!TextUtils.isEmpty(phoneticFamilyName)) } } @@ -903,21 +961,21 @@ public class VCardBuilder { 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 = @@ -940,16 +998,16 @@ public class VCardBuilder { // 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()); } } @@ -1146,6 +1204,8 @@ public class VCardBuilder { } 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; @@ -1213,16 +1273,19 @@ public class VCardBuilder { 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 = ""; @@ -1537,7 +1600,8 @@ public class VCardBuilder { 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; } @@ -1659,7 +1723,7 @@ public class VCardBuilder { 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); } @@ -1794,9 +1858,9 @@ public class VCardBuilder { 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(); } diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java index dc0d864..1bc7785 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -41,6 +41,7 @@ import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; +import android.text.TextUtils; import android.util.CharsetUtils; import android.util.Log; @@ -61,15 +62,11 @@ import java.util.Map; /** * <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); @@ -94,14 +91,17 @@ import java.util.Map; * composer.terminate(); * } * } </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> + * In default, Default {@link VCardBuilder} class 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 class VCardComposer { 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 @@ public class VCardComposer { 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 @@ public class VCardComposer { /** * <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 { @SuppressWarnings("hiding") private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream"; - final private OutputStream mOutputStream; // mWriter will close this. - private Writer mWriter; - private boolean mOnTerminateIsCalled = false; + final private OutputStream mOutputStream; // mWriter will close this. + protected 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 final 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; } @@ -205,7 +207,7 @@ public class VCardComposer { return true; } - public boolean onEntryCreated(String vcard) { + public final boolean onEntryCreated(String vcard) { try { mWriter.write(vcard); } catch (IOException e) { @@ -218,7 +220,7 @@ public class VCardComposer { return true; } - public void onTerminate() { + public final void onTerminate() { mOnTerminateIsCalled = true; if (mWriter != null) { try { @@ -235,14 +237,21 @@ public class VCardComposer { "IOException during closing the output stream: " + e.getMessage()); } finally { - try { - mWriter.close(); - } catch (IOException e) { - } + closeOutputStream(); } } } + // Users can override this if they want to (e.g. if they don't want to close the stream). + // TODO: Should expose bare OutputStream instead? + 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 +266,10 @@ public class VCardComposer { 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,11 +280,14 @@ public class VCardComposer { }; 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) { @@ -284,9 +295,25 @@ public class VCardComposer { } /** + * 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, + String charset, final boolean careHandlerErrors) { mContext = context; mVCardType = vcardType; @@ -294,30 +321,62 @@ public class VCardComposer { 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; - } - 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; + if (mIsDoCoMo || VCardConfig.shouldUseShiftJisForExport(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; } 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 +410,7 @@ public class VCardComposer { } 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 +473,7 @@ public class VCardComposer { mErrorReason = FAILURE_REASON_NOT_INITIALIZED; return false; } - String vcard; + final String vcard; try { if (mIdColumn >= 0) { vcard = createOneEntryInternal(mCursor.getString(mIdColumn), @@ -437,8 +496,7 @@ public class VCardComposer { 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 +515,7 @@ public class VCardComposer { } 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. @@ -527,20 +585,34 @@ public class VCardComposer { } } - 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)) - .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(); + 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); + // TODO: Android-specific X attributes? + 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(); + } } public void terminate() { @@ -563,26 +635,38 @@ public class VCardComposer { @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 3442ae7..40566ca 100644 --- a/core/java/android/pim/vcard/VCardConfig.java +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -43,10 +43,28 @@ public class VCardConfig { /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones. /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4; - // 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"; - + /** + * <P> + * The charset used during import. + * </P> + * <P> + * We cannot determine which charset should be used to interpret a given vCard file, + * while we have to decode sime encoded data (e.g. BASE64) to binary. + * In order to avoid "misinterpretation" of charset as much as possible, + * "ISO-8859-1" (a.k.a Latin-1) is first used for reading a stream. + * When charset is specified in a property (with "CHARSET=..." parameter), + * the string is decoded to raw bytes and encoded into the specific charset, + * assuming "ISO-8859-1" is able to map "all" 8bit characters to some unicode, + * and it has 1 to 1 mapping in all 8bit characters. + * If the assumption is not correct, this setting will cause some bug. + * </P> + */ + /* package */ static final String DEFAULT_TEMPORARY_CHARSET = "ISO-8859-1"; + + // TODO: still intermediate procedures uses this charset. Fix it. + public static final String DEFAULT_IMPORT_CHARSET = "ISO-8859-1"; + public static final String DEFAULT_EXPORT_CHARSET = "UTF-8"; + public static final int FLAG_V21 = 0; public static final int FLAG_V30 = 1; @@ -58,10 +76,13 @@ public class VCardConfig { 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; + + /* + * These flags are ignored when charset is explicitly given by a caller. + */ + private static final int FLAG_USE_UTF8_FOR_EXPORT = 0; + private static final int FLAG_USE_SHIFT_JIS_FOR_EXPORT = 0x100; + private static final int FLAG_CHARSET_MASK_FOR_EKPORT = 0xF00; /** * The flag indicating the vCard composer will add some "X-" properties used only in Android @@ -196,7 +217,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V21_GENERIC_UTF8 = - (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | + (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic"; @@ -210,7 +231,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V30_GENERIC_UTF8 = - (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | + (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic"; @@ -222,7 +243,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V21_EUROPE_UTF8 = - (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | + (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe"; @@ -236,7 +257,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V30_EUROPE_UTF8 = - (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | + (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe"; @@ -250,7 +271,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V21_JAPANESE_UTF8 = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | + (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8"; @@ -265,7 +286,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V21_JAPANESE_SJIS = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | + (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis"; @@ -280,7 +301,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V30_JAPANESE_SJIS = - (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | + (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis"; @@ -294,7 +315,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V30_JAPANESE_UTF8 = - (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | + (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_USE_UTF8_FOR_EXPORT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8"; @@ -310,7 +331,7 @@ public class VCardConfig { * </P> */ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | + (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_USE_SHIFT_JIS_FOR_EXPORT | FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES); @@ -379,12 +400,17 @@ public class VCardConfig { return !isV30(vcardType); } - public static boolean usesUtf8(final int vcardType) { - return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8); + /* package */ static boolean shouldUseUtf8ForExport(final int vcardType) { + return ((vcardType & FLAG_CHARSET_MASK_FOR_EKPORT) == FLAG_USE_UTF8_FOR_EXPORT); } - public static boolean usesShiftJis(final int vcardType) { - return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS); + /** + * Shift_JIS (a charset for Japanese text files) needs special handling to select + * carrer specific variants. + * @hide just for test + */ + public static boolean shouldUseShiftJisForExport(final int vcardType) { + return ((vcardType & FLAG_CHARSET_MASK_FOR_EKPORT) == FLAG_USE_SHIFT_JIS_FOR_EXPORT); } public static int getNameOrderType(final int vcardType) { diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java index 290ca2b..4efb105 100644 --- a/core/java/android/pim/vcard/VCardEntryConstructor.java +++ b/core/java/android/pim/vcard/VCardEntryConstructor.java @@ -80,7 +80,7 @@ public class VCardEntryConstructor implements VCardInterpreter { if (inputCharset != null) { mInputCharset = inputCharset; } else { - mInputCharset = VCardConfig.DEFAULT_CHARSET; + mInputCharset = VCardConfig.DEFAULT_TEMPORARY_CHARSET; } if (charsetForDetodedBytes != null) { mCharsetForDecodedBytes = charsetForDetodedBytes; diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java index 57c52a6..b38ea93 100644 --- a/core/java/android/pim/vcard/VCardParser.java +++ b/core/java/android/pim/vcard/VCardParser.java @@ -34,26 +34,17 @@ public abstract class VCardParser { /** * <P> - * Parses the given stream and send the VCard data into VCardBuilderBase object. + * Parses the given stream and send the vCard data into VCardBuilderBase object. * </P. * <P> * 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) + * 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> - * 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. + * 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. @@ -61,27 +52,24 @@ public abstract class VCardParser { * @return Returns true for success. Otherwise returns false. * @throws IOException, VCardException */ - public abstract boolean parse(InputStream is, VCardInterpreter interepreter) - throws IOException, VCardException; - + public final boolean parse(InputStream is, VCardInterpreter interepreter) + throws IOException, VCardException { + return parse(is, VCardConfig.DEFAULT_TEMPORARY_CHARSET, interepreter); + } + /** * <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. + * @param interpreter The VCardBuilderBase object. * @return Returns true when successful. Otherwise returns false. * @throws IOException, VCardException */ - public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder) + public abstract boolean parse(InputStream is, String charset, + VCardInterpreter interpreter) throws IOException, VCardException; /** diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java index fe8cfb0..4f85150 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -68,7 +68,28 @@ public class VCardParser_V21 extends VCardParser { private static final HashSet<String> sAvailableEncodingV21 = new HashSet<String>(Arrays.asList( "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B")); - + + private static final 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; + } + } + // Used only for parsing END:VCARD. private String mPreviousLine; @@ -839,19 +860,17 @@ public class VCardParser_V21 extends VCardParser { 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 (is == null) { + throw new NullPointerException("InputStream must not be null."); + } if (charset == null) { - charset = VCardConfig.DEFAULT_CHARSET; + charset = VCardConfig.DEFAULT_TEMPORARY_CHARSET; } + final InputStreamReader tmpReader = new InputStreamReader(is, charset); if (VCardConfig.showPerformanceLog()) { mReader = new CustomBufferedReader(tmpReader); @@ -913,24 +932,3 @@ public class VCardParser_V21 extends VCardParser { 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; - } -} |
