diff options
Diffstat (limited to 'core/java/android/pim/vcard/VCardComposer.java')
-rw-r--r-- | core/java/android/pim/vcard/VCardComposer.java | 675 |
1 files changed, 0 insertions, 675 deletions
diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java deleted file mode 100644 index 170d6fa..0000000 --- a/core/java/android/pim/vcard/VCardComposer.java +++ /dev/null @@ -1,675 +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.Context; -import android.content.Entity; -import android.content.EntityIterator; -import android.content.Entity.NamedContentValues; -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; -import android.provider.ContactsContract.CommonDataKinds.Nickname; -import android.provider.ContactsContract.CommonDataKinds.Note; -import android.provider.ContactsContract.CommonDataKinds.Organization; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.CommonDataKinds.Photo; -import android.provider.ContactsContract.CommonDataKinds.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; - -import java.io.BufferedWriter; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.charset.UnsupportedCharsetException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * <p> - * 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); - * composer.addHandler( - * composer.new HandlerForOutputStream(outputStream)); - * if (!composer.init()) { - * // Do something handling the situation. - * return; - * } - * while (!composer.isAfterLast()) { - * if (mCanceled) { - * // Assume a user may cancel this operation during the export. - * return; - * } - * if (!composer.createOneEntry()) { - * // Do something handling the error situation. - * return; - * } - * } - * } finally { - * if (composer != null) { - * 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> - * {@link VCardBuilder} is used to build each vCard. - * </p> - */ -public class VCardComposer { - private static final String LOG_TAG = "VCardComposer"; - - public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO = - "Failed to get database information"; - - public static final String FAILURE_REASON_NO_ENTRY = - "There's no exportable in the database"; - - public static final String FAILURE_REASON_NOT_INITIALIZED = - "The vCard composer object is not correctly initialized"; - - /** Should be visible only from developers... (no need to translate, hopefully) */ - public static final String FAILURE_REASON_UNSUPPORTED_URI = - "The Uri vCard composer received is not supported by the composer."; - - public static final String NO_ERROR = "No error"; - - 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"; - - /** - * Special URI for testing. - */ - public static final String VCARD_TEST_AUTHORITY = "com.android.unit_tests.vcard"; - public static final Uri VCARD_TEST_AUTHORITY_URI = - Uri.parse("content://" + VCARD_TEST_AUTHORITY); - public static final Uri CONTACTS_TEST_CONTENT_URI = - Uri.withAppendedPath(VCARD_TEST_AUTHORITY_URI, "contacts"); - - private static final Map<Integer, String> sImMap; - - static { - sImMap = new HashMap<Integer, String>(); - sImMap.put(Im.PROTOCOL_AIM, VCardConstants.PROPERTY_X_AIM); - sImMap.put(Im.PROTOCOL_MSN, VCardConstants.PROPERTY_X_MSN); - sImMap.put(Im.PROTOCOL_YAHOO, VCardConstants.PROPERTY_X_YAHOO); - 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); - // We don't add Google talk here since it has to be handled separately. - } - - public static interface OneEntryHandler { - public boolean onInit(Context context); - public boolean onEntryCreated(String vcard); - public void onTerminate(); - } - - /** - * <p> - * 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 this class. - * </p> - */ - public final class HandlerForOutputStream implements OneEntryHandler { - @SuppressWarnings("hiding") - 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(final OutputStream outputStream) { - mOutputStream = outputStream; - } - - public boolean onInit(final Context context) { - try { - mWriter = new BufferedWriter(new OutputStreamWriter( - mOutputStream, mCharset)); - } catch (UnsupportedEncodingException e1) { - Log.e(LOG_TAG, "Unsupported charset: " + mCharset); - mErrorReason = "Encoding is not supported (usually this does not happen!): " - + mCharset; - return false; - } - - if (mIsDoCoMo) { - try { - // Create one empty entry. - mWriter.write(createOneEntryInternal("-1", null)); - } catch (VCardException e) { - Log.e(LOG_TAG, "VCardException has been thrown during on Init(): " + - e.getMessage()); - return false; - } catch (IOException e) { - Log.e(LOG_TAG, - "IOException occurred during exportOneContactData: " - + e.getMessage()); - mErrorReason = "IOException occurred: " + e.getMessage(); - return false; - } - } - return true; - } - - public boolean onEntryCreated(String vcard) { - try { - mWriter.write(vcard); - } catch (IOException e) { - Log.e(LOG_TAG, - "IOException occurred during exportOneContactData: " - + e.getMessage()); - mErrorReason = "IOException occurred: " + e.getMessage(); - return false; - } - return true; - } - - public void onTerminate() { - mOnTerminateIsCalled = true; - if (mWriter != null) { - try { - // Flush and sync the data so that a user is able to pull - // the SDCard just after - // the export. - mWriter.flush(); - if (mOutputStream != null - && mOutputStream instanceof FileOutputStream) { - ((FileOutputStream) mOutputStream).getFD().sync(); - } - } catch (IOException e) { - Log.d(LOG_TAG, - "IOException during closing the output stream: " - + e.getMessage()); - } finally { - 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) { - onTerminate(); - } - } - } - - private final Context mContext; - private final int mVCardType; - private final boolean mCareHandlerErrors; - private final ContentResolver mContentResolver; - - private final boolean mIsDoCoMo; - private Cursor mCursor; - private int mIdColumn; - - private final String mCharset; - private boolean mTerminateIsCalled; - private final List<OneEntryHandler> mHandlerList; - - private String mErrorReason = NO_ERROR; - - private static final String[] sContactsProjection = new String[] { - Contacts._ID, - }; - - public VCardComposer(Context context) { - 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, null, true); - } - - 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, String charset, - final boolean careHandlerErrors) { - mContext = context; - mVCardType = vcardType; - mCareHandlerErrors = careHandlerErrors; - mContentResolver = context.getContentResolver(); - - mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); - mHandlerList = new ArrayList<OneEntryHandler>(); - - charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset); - final boolean shouldAppendCharsetParam = !( - VCardConfig.isV30(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; - } - } - } else { - 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 + "\""); - } - - /** - * Must be called before {@link #init()}. - */ - public void addHandler(OneEntryHandler handler) { - if (handler != null) { - mHandlerList.add(handler); - } - } - - /** - * @return Returns true when initialization is successful and all the other - * methods are available. Returns false otherwise. - */ - public boolean init() { - return init(null, null); - } - - public boolean init(final String selection, final String[] selectionArgs) { - return init(Contacts.CONTENT_URI, selection, selectionArgs, null); - } - - /** - * Note that this is unstable interface, may be deleted in the future. - */ - public boolean init(final Uri contentUri, final String selection, - final String[] selectionArgs, final String sortOrder) { - if (contentUri == null) { - return false; - } - - if (mCareHandlerErrors) { - final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( - mHandlerList.size()); - for (OneEntryHandler handler : mHandlerList) { - if (!handler.onInit(mContext)) { - for (OneEntryHandler finished : finishedList) { - finished.onTerminate(); - } - return false; - } - } - } else { - // Just ignore the false returned from onInit(). - for (OneEntryHandler handler : mHandlerList) { - handler.onInit(mContext); - } - } - - final String[] projection; - if (Contacts.CONTENT_URI.equals(contentUri) || - CONTACTS_TEST_CONTENT_URI.equals(contentUri)) { - projection = sContactsProjection; - } else { - mErrorReason = FAILURE_REASON_UNSUPPORTED_URI; - return false; - } - mCursor = mContentResolver.query( - contentUri, projection, selection, selectionArgs, sortOrder); - - if (mCursor == null) { - mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO; - return false; - } - - if (getCount() == 0 || !mCursor.moveToFirst()) { - try { - mCursor.close(); - } catch (SQLiteException e) { - Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); - } finally { - mCursor = null; - mErrorReason = FAILURE_REASON_NO_ENTRY; - } - return false; - } - - mIdColumn = mCursor.getColumnIndex(Contacts._ID); - - return true; - } - - public boolean createOneEntry() { - return createOneEntry(null); - } - - /** - * @param getEntityIteratorMethod For Dependency Injection. - * @hide just for testing. - */ - public boolean createOneEntry(Method getEntityIteratorMethod) { - if (mCursor == null || mCursor.isAfterLast()) { - mErrorReason = FAILURE_REASON_NOT_INITIALIZED; - return false; - } - final String vcard; - try { - if (mIdColumn >= 0) { - vcard = createOneEntryInternal(mCursor.getString(mIdColumn), - getEntityIteratorMethod); - } else { - Log.e(LOG_TAG, "Incorrect mIdColumn: " + mIdColumn); - return true; - } - } catch (VCardException e) { - Log.e(LOG_TAG, "VCardException has been thrown: " + e.getMessage()); - return false; - } catch (OutOfMemoryError error) { - // Maybe some data (e.g. photo) is too big to have in memory. But it - // should be rare. - Log.e(LOG_TAG, "OutOfMemoryError occured. Ignore the entry."); - System.gc(); - // TODO: should tell users what happened? - return true; - } finally { - mCursor.moveToNext(); - } - - // This function does not care the OutOfMemoryError on the handler side :-P - if (mCareHandlerErrors) { - List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( - mHandlerList.size()); - for (OneEntryHandler handler : mHandlerList) { - if (!handler.onEntryCreated(vcard)) { - return false; - } - } - } else { - for (OneEntryHandler handler : mHandlerList) { - handler.onEntryCreated(vcard); - } - } - - return true; - } - - private String createOneEntryInternal(final String contactId, - 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. - // e.g. If all the data in the contact of the given contact id are not exportable ones, - // they are hidden from the view of this method, though contact id itself exists. - EntityIterator entityIterator = null; - try { - final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon() - .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 unit tests only - try { - entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null, - mContentResolver, uri, selection, selectionArgs, null); - } catch (IllegalArgumentException e) { - Log.e(LOG_TAG, "IllegalArgumentException has been thrown: " + - e.getMessage()); - } catch (IllegalAccessException e) { - Log.e(LOG_TAG, "IllegalAccessException has been thrown: " + - e.getMessage()); - } catch (InvocationTargetException e) { - Log.e(LOG_TAG, "InvocationTargetException has been thrown: "); - StackTraceElement[] stackTraceElements = e.getCause().getStackTrace(); - for (StackTraceElement element : stackTraceElements) { - Log.e(LOG_TAG, " at " + element.toString()); - } - throw new VCardException("InvocationTargetException has been thrown: " + - e.getCause().getMessage()); - } - } else { - entityIterator = RawContacts.newEntityIterator(mContentResolver.query( - uri, null, selection, selectionArgs, null)); - } - - if (entityIterator == null) { - Log.e(LOG_TAG, "EntityIterator is null"); - return ""; - } - - if (!entityIterator.hasNext()) { - Log.w(LOG_TAG, "Data does not exist. contactId: " + contactId); - return ""; - } - - while (entityIterator.hasNext()) { - Entity entity = entityIterator.next(); - for (NamedContentValues namedContentValues : entity.getSubValues()) { - ContentValues contentValues = namedContentValues.values; - String key = contentValues.getAsString(Data.MIMETYPE); - if (key != null) { - List<ContentValues> contentValuesList = - contentValuesListMap.get(key); - if (contentValuesList == null) { - contentValuesList = new ArrayList<ContentValues>(); - contentValuesListMap.put(key, contentValuesList); - } - contentValuesList.add(contentValues); - } - } - } - } finally { - if (entityIterator != null) { - entityIterator.close(); - } - } - - 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)) - .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() { - for (OneEntryHandler handler : mHandlerList) { - handler.onTerminate(); - } - - if (mCursor != null) { - try { - mCursor.close(); - } catch (SQLiteException e) { - Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage()); - } - mCursor = null; - } - - mTerminateIsCalled = true; - } - - @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 Returns the error reason. - */ - public String getErrorReason() { - return mErrorReason; - } -} |