VCardComposerpublic 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_URIShould 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 | mFirstVCardEmittedInDoCoMoCaseUsed 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 | mTerminateCalledSet 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.
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.
// 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.String | buildVCard(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 void | closeCursorIfAppropriate()
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.String | createOneEntry()
return createOneEntry(null);
| public java.lang.String | createOneEntry(java.lang.reflect.Method getEntityIteratorMethod)
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.String | createOneEntryInternal(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 void | finalize()
try {
if (!mTerminateCalled) {
Log.e(LOG_TAG, "finalized() is called before terminate() being called");
}
} finally {
super.finalize();
}
| public int | getCount()
if (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return 0;
}
return mCursor.getCount();
| public java.lang.String | getErrorReason()
return mErrorReason;
| public boolean | init(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 boolean | init(android.net.Uri contentUri, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder, android.net.Uri contentUriForRawContactsEntity)
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder,
contentUriForRawContactsEntity);
| public boolean | init(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.
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 boolean | init(android.database.Cursor cursor)Just for testing for now. Do not use.
if (!initInterFirstPart(null)) {
return false;
}
mCursorSuppliedFromOutside = true;
mCursor = cursor;
if (!initInterMainPart()) {
return false;
}
return initInterLastPart();
| public boolean | init()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 init(null, null);
| public boolean | init(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 boolean | initInterCursorCreationPart(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 boolean | initInterFirstPart(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 boolean | initInterLastPart()
mInitDone = true;
mTerminateCalled = false;
return true;
| private boolean | initInterMainPart()
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 boolean | initWithRawContactsEntityUri(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.
return init(Contacts.CONTENT_URI, sContactsProjection, null, null, null,
contentUriForRawContactsEntity);
| public boolean | isAfterLast()
if (mCursor == null) {
Log.w(LOG_TAG, "This object is not ready yet.");
return false;
}
return mCursor.isAfterLast();
| public void | setPhoneNumberTranslationCallback(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 void | terminate()
closeCursorIfAppropriate();
mTerminateCalled = true;
|
|