FileDocCategorySizeDatePackage
VCardComposer.javaAPI DocAndroid 5.1 API24973Thu Mar 12 22:22:54 GMT 2015com.android.vcard

VCardComposer

public class VCardComposer extends Object

The class for composing vCard from Contacts information.

Usually, this class should be used like this.

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();
}
}

Users have to manually take care of memory efficiency. Even one vCard may contain image of non-trivial size for mobile devices.

{@link VCardBuilder} is used to build each vCard.

Fields Summary
private static final String
LOG_TAG
private static final boolean
DEBUG
public static final String
FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO
public static final String
FAILURE_REASON_NO_ENTRY
public static final String
FAILURE_REASON_NOT_INITIALIZED
public static final String
FAILURE_REASON_UNSUPPORTED_URI
Should be visible only from developers... (no need to translate, hopefully)
public static final String
NO_ERROR
private static final String
SHIFT_JIS
private static final String
UTF_8
private static final Map
sImMap
private final int
mVCardType
private final android.content.ContentResolver
mContentResolver
private final boolean
mIsDoCoMo
private boolean
mFirstVCardEmittedInDoCoMoCase
Used only when {@link #mIsDoCoMo} is true. Set to true when the first vCard for DoCoMo vCard is emitted.
private android.database.Cursor
mCursor
private boolean
mCursorSuppliedFromOutside
private int
mIdColumn
private android.net.Uri
mContentUriForRawContactsEntity
private final String
mCharset
private boolean
mInitDone
private String
mErrorReason
private boolean
mTerminateCalled
Set to false when one of {@link #init()} variants is called, and set to true when {@link #terminate()} is called. Initially set to true.
private static final String[]
sContactsProjection
private VCardPhoneNumberTranslationCallback
mPhoneTranslationCallback
Constructors Summary
public VCardComposer(android.content.Context context)


       
        this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true);
    
public VCardComposer(android.content.Context context, int vcardType)
The variant which sets charset to null and sets careHandlerErrors to true.

        this(context, vcardType, null, true);
    
public VCardComposer(android.content.Context context, int vcardType, String charset)

        this(context, vcardType, charset, true);
    
public VCardComposer(android.content.Context context, int vcardType, boolean careHandlerErrors)
The variant which sets charset to null.

        this(context, vcardType, null, careHandlerErrors);
    
public VCardComposer(android.content.Context context, int vcardType, String charset, boolean careHandlerErrors)
Constructs 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

        this(context, context.getContentResolver(), vcardType, charset, careHandlerErrors);
    
public VCardComposer(android.content.Context context, android.content.ContentResolver resolver, int vcardType, String charset, boolean careHandlerErrors)
Just for testing for now.

param
resolver {@link ContentResolver} which used by this object.
hide

        // Not used right now
        // mContext = context;
        mVCardType = vcardType;
        mContentResolver = resolver;

        mIsDoCoMo = VCardConfig.isDoCoMo(vcardType);

        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)) {
                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 {
                    mCharset = charset;
                }
            }
        } else {
            if (TextUtils.isEmpty(charset)) {
                mCharset = UTF_8;
            } else {
                mCharset = charset;
            }
        }

        Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\"");
    
Methods Summary
public java.lang.StringbuildVCard(java.util.Map 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.

        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),
                            mPhoneTranslationCallback)
                    .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))
                    .appendSipAddresses(contentValuesListMap.get(SipAddress.CONTENT_ITEM_TYPE))
                    .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE));
            return builder.toString();
        }
    
private voidcloseCursorIfAppropriate()

        if (!mCursorSuppliedFromOutside && mCursor != null) {
            try {
                mCursor.close();
            } catch (SQLiteException e) {
                Log.e(LOG_TAG, "SQLiteException on Cursor#close(): " + e.getMessage());
            }
            mCursor = null;
        }
    
public java.lang.StringcreateOneEntry()

return
a vCard string.

        return createOneEntry(null);
    
public java.lang.StringcreateOneEntry(java.lang.reflect.Method getEntityIteratorMethod)

hide

        if (mIsDoCoMo && !mFirstVCardEmittedInDoCoMoCase) {
            mFirstVCardEmittedInDoCoMoCase = true;
            // Previously we needed to emit empty data for this specific case, but actually
            // this doesn't work now, as resolver doesn't return any data with "-1" contactId.
            // TODO: re-introduce or remove this logic. Needs to modify unit test when we
            // re-introduce the logic.
            // return createOneEntryInternal("-1", getEntityIteratorMethod);
        }

        final String vcard = createOneEntryInternal(mCursor.getString(mIdColumn),
                getEntityIteratorMethod);
        if (!mCursor.moveToNext()) {
            Log.e(LOG_TAG, "Cursor#moveToNext() returned false");
        }
        return vcard;
    
private java.lang.StringcreateOneEntryInternal(java.lang.String contactId, java.lang.reflect.Method getEntityIteratorMethod)

        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 = mContentUriForRawContactsEntity;
            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: ", e);
                    throw new RuntimeException("InvocationTargetException has been thrown");
                }
            } 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);
    
protected voidfinalize()

        try {
            if (!mTerminateCalled) {
                Log.e(LOG_TAG, "finalized() is called before terminate() being called");
            }
        } finally {
            super.finalize();
        }
    
public intgetCount()

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).

        if (mCursor == null) {
            Log.w(LOG_TAG, "This object is not ready yet.");
            return 0;
        }
        return mCursor.getCount();
    
public java.lang.StringgetErrorReason()

return
Returns the error reason.

        return mErrorReason;
    
public booleaninit(android.net.Uri contentUri, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)
Note that this is unstable interface, may be deleted in the future.

        return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder, null);
    
public booleaninit(android.net.Uri contentUri, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder, android.net.Uri contentUriForRawContactsEntity)

param
contentUri Uri for obtaining the list of contactId. Used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
selection selection used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
selectionArgs selectionArgs used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
sortOrder sortOrder used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
contentUriForRawContactsEntity Uri for obtaining entries relevant to each contactId. Note that this is an unstable interface, may be deleted in the future.

        return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder,
                contentUriForRawContactsEntity);
    
public booleaninit(android.net.Uri contentUri, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder, android.net.Uri contentUriForRawContactsEntity)
A variant of init(). Currently just for testing. Use other variants for init(). First we'll create {@link Cursor} for the list of contactId. Cursor cursorForId = mContentResolver.query( contentUri, projection, selection, selectionArgs, sortOrder); After that, we'll obtain data for each contactId in the list. Cursor cursorForContent = mContentResolver.query( contentUriForRawContactsEntity, null, Data.CONTACT_ID + "=?", new String[] {contactId}, null) {@link #createOneEntry()} or its variants let the caller obtain each entry from cursorForContent above.

param
contentUri Uri for obtaining the list of contactId. Used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
projection projection used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
selection selection used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
selectionArgs selectionArgs used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
sortOrder sortOrder used with {@link ContentResolver#query(Uri, String[], String, String[], String)}
param
contentUriForRawContactsEntity Uri for obtaining entries relevant to each contactId.
return
true when successful
hide

        if (!ContactsContract.AUTHORITY.equals(contentUri.getAuthority())) {
            if (DEBUG) Log.d(LOG_TAG, "Unexpected contentUri: " + contentUri);
            mErrorReason = FAILURE_REASON_UNSUPPORTED_URI;
            return false;
        }

        if (!initInterFirstPart(contentUriForRawContactsEntity)) {
            return false;
        }
        if (!initInterCursorCreationPart(contentUri, projection, selection, selectionArgs,
                sortOrder)) {
            return false;
        }
        if (!initInterMainPart()) {
            return false;
        }
        return initInterLastPart();
    
public booleaninit(android.database.Cursor cursor)
Just for testing for now. Do not use.

hide

        if (!initInterFirstPart(null)) {
            return false;
        }
        mCursorSuppliedFromOutside = true;
        mCursor = cursor;
        if (!initInterMainPart()) {
            return false;
        }
        return initInterLastPart();
    
public booleaninit()
Initializes this object using default {@link Contacts#CONTENT_URI}. You can call this method or a variant of this method just once. In other words, you cannot reuse this object.

return
Returns true when initialization is successful and all the other methods are available. Returns false otherwise.

        return init(null, null);
    
public booleaninit(java.lang.String selection, java.lang.String[] selectionArgs)
Initializes this object using default {@link Contacts#CONTENT_URI} and given selection arguments.

        return init(Contacts.CONTENT_URI, sContactsProjection, selection, selectionArgs,
                null, null);
    
private booleaninitInterCursorCreationPart(android.net.Uri contentUri, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)

        mCursorSuppliedFromOutside = false;
        mCursor = mContentResolver.query(
                contentUri, projection, selection, selectionArgs, sortOrder);
        if (mCursor == null) {
            Log.e(LOG_TAG, String.format("Cursor became null unexpectedly"));
            mErrorReason = FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO;
            return false;
        }
        return true;
    
private booleaninitInterFirstPart(android.net.Uri contentUriForRawContactsEntity)

        mContentUriForRawContactsEntity =
                (contentUriForRawContactsEntity != null ? contentUriForRawContactsEntity :
                        RawContactsEntity.CONTENT_URI);
        if (mInitDone) {
            Log.e(LOG_TAG, "init() is already called");
            return false;
        }
        return true;
    
private booleaninitInterLastPart()

        mInitDone = true;
        mTerminateCalled = false;
        return true;
    
private booleaninitInterMainPart()

        if (mCursor.getCount() == 0 || !mCursor.moveToFirst()) {
            if (DEBUG) {
                Log.d(LOG_TAG,
                    String.format("mCursor has an error (getCount: %d): ", mCursor.getCount()));
            }
            closeCursorIfAppropriate();
            return false;
        }
        mIdColumn = mCursor.getColumnIndex(Contacts._ID);
        return mIdColumn >= 0;
    
public booleaninitWithRawContactsEntityUri(android.net.Uri contentUriForRawContactsEntity)
Special variant of init(), which accepts a Uri for obtaining {@link RawContactsEntity} from {@link ContentResolver} with {@link Contacts#_ID}. String selection = Data.CONTACT_ID + "=?"; String[] selectionArgs = new String[] {contactId}; Cursor cursor = mContentResolver.query( contentUriForRawContactsEntity, null, selection, selectionArgs, null) You can call this method or a variant of this method just once. In other words, you cannot reuse this object.

deprecated
Use {@link #init(Uri, String[], String, String[], String, Uri)} if you really need to change the default Uri.

        return init(Contacts.CONTENT_URI, sContactsProjection, null, null, null,
                contentUriForRawContactsEntity);
    
public booleanisAfterLast()

return
true when there's no entity to be built. The return value is undefined when this object is not ready yet.

        if (mCursor == null) {
            Log.w(LOG_TAG, "This object is not ready yet.");
            return false;
        }
        return mCursor.isAfterLast();
    
public voidsetPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback callback)

Set a callback for phone number formatting. It will be called every time when this object receives a phone number for printing.

When this is set {@link VCardConfig#FLAG_REFRAIN_PHONE_NUMBER_FORMATTING} will be ignored and the callback should be responsible for everything about phone number formatting.

Caution: This interface will change. Please don't use without any strong reason.

        mPhoneTranslationCallback = callback;
    
public voidterminate()

        closeCursorIfAppropriate();
        mTerminateCalled = true;