699 lines
27 KiB
Java
699 lines
27 KiB
Java
/*
|
|
* 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 com.android.vcard;
|
|
|
|
import android.content.ContentResolver;
|
|
import android.content.ContentValues;
|
|
import android.content.Context;
|
|
import android.content.Entity;
|
|
import android.content.Entity.NamedContentValues;
|
|
import android.content.EntityIterator;
|
|
import android.database.Cursor;
|
|
import android.database.sqlite.SQLiteException;
|
|
import android.net.Uri;
|
|
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.SipAddress;
|
|
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
|
|
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
|
|
import android.provider.ContactsContract.CommonDataKinds.Website;
|
|
import android.provider.ContactsContract.Contacts;
|
|
import android.provider.ContactsContract.Data;
|
|
import android.provider.ContactsContract.RawContacts;
|
|
import android.provider.ContactsContract.RawContactsEntity;
|
|
import android.provider.ContactsContract;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
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";
|
|
private static final boolean DEBUG = false;
|
|
|
|
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";
|
|
|
|
// 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";
|
|
|
|
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.
|
|
}
|
|
|
|
private final int mVCardType;
|
|
private final ContentResolver mContentResolver;
|
|
|
|
private final boolean mIsDoCoMo;
|
|
/**
|
|
* Used only when {@link #mIsDoCoMo} is true. Set to true when the first vCard for DoCoMo
|
|
* vCard is emitted.
|
|
*/
|
|
private boolean mFirstVCardEmittedInDoCoMoCase;
|
|
|
|
private Cursor mCursor;
|
|
private boolean mCursorSuppliedFromOutside;
|
|
private int mIdColumn;
|
|
private Uri mContentUriForRawContactsEntity;
|
|
|
|
private final String mCharset;
|
|
|
|
private boolean mInitDone;
|
|
private String mErrorReason = NO_ERROR;
|
|
|
|
/**
|
|
* 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 boolean mTerminateCalled = true;
|
|
|
|
private RawContactEntitlesInfoCallback mRawContactEntitlesInfoCallback;
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
public VCardComposer(final Context context, final int vcardType, String charset,
|
|
final boolean careHandlerErrors) {
|
|
this(context, context.getContentResolver(), vcardType, charset, careHandlerErrors);
|
|
}
|
|
|
|
/**
|
|
* Just for testing for now.
|
|
* @param resolver {@link ContentResolver} which used by this object.
|
|
* @hide
|
|
*/
|
|
public VCardComposer(final Context context, ContentResolver resolver,
|
|
final int vcardType, String charset, final boolean careHandlerErrors) {
|
|
// 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 + "\"");
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
public boolean init() {
|
|
return init(null, null);
|
|
}
|
|
|
|
/**
|
|
* Special variant of init(), which accepts a Uri for obtaining {@link RawContactsEntity} from
|
|
* {@link ContentResolver} with {@link Contacts#_ID}.
|
|
* <code>
|
|
* String selection = Data.CONTACT_ID + "=?";
|
|
* String[] selectionArgs = new String[] {contactId};
|
|
* Cursor cursor = mContentResolver.query(
|
|
* contentUriForRawContactsEntity, null, selection, selectionArgs, null)
|
|
* </code>
|
|
*
|
|
* 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.
|
|
*/
|
|
@Deprecated
|
|
public boolean initWithRawContactsEntityUri(Uri contentUriForRawContactsEntity) {
|
|
return init(Contacts.CONTENT_URI, sContactsProjection, null, null, null,
|
|
contentUriForRawContactsEntity);
|
|
}
|
|
|
|
/**
|
|
* Initializes this object using default {@link Contacts#CONTENT_URI} and given selection
|
|
* arguments.
|
|
*/
|
|
public boolean init(final String selection, final String[] selectionArgs) {
|
|
return init(Contacts.CONTENT_URI, sContactsProjection, selection, selectionArgs,
|
|
null, 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) {
|
|
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder, null);
|
|
}
|
|
|
|
/**
|
|
* @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.
|
|
*/
|
|
public boolean init(final Uri contentUri, final String selection,
|
|
final String[] selectionArgs, final String sortOrder,
|
|
final Uri contentUriForRawContactsEntity) {
|
|
return init(contentUri, sContactsProjection, selection, selectionArgs, sortOrder,
|
|
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.
|
|
*
|
|
* <code>
|
|
* Cursor cursorForId = mContentResolver.query(
|
|
* contentUri, projection, selection, selectionArgs, sortOrder);
|
|
* </code>
|
|
*
|
|
* After that, we'll obtain data for each contactId in the list.
|
|
*
|
|
* <code>
|
|
* Cursor cursorForContent = mContentResolver.query(
|
|
* contentUriForRawContactsEntity, null,
|
|
* Data.CONTACT_ID + "=?", new String[] {contactId}, null)
|
|
* </code>
|
|
*
|
|
* {@link #createOneEntry()} or its variants let the caller obtain each entry from
|
|
* <code>cursorForContent</code> 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
|
|
*/
|
|
public boolean init(final Uri contentUri, final String[] projection,
|
|
final String selection, final String[] selectionArgs,
|
|
final String sortOrder, Uri contentUriForRawContactsEntity) {
|
|
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();
|
|
}
|
|
|
|
/**
|
|
* Just for testing for now. Do not use.
|
|
* @hide
|
|
*/
|
|
public boolean init(Cursor cursor) {
|
|
return initWithCallback(cursor, null);
|
|
}
|
|
|
|
/**
|
|
* @param cursor Cursor that used to get contact id
|
|
* @param rawContactEntitlesInfoCallback Callback that return RawContactEntitlesInfo
|
|
* Note that this is an unstable interface, may be deleted in the future.
|
|
*
|
|
* @return true when successful
|
|
*/
|
|
public boolean initWithCallback(Cursor cursor,
|
|
RawContactEntitlesInfoCallback rawContactEntitlesInfoCallback) {
|
|
if (!initInterFirstPart(null)) {
|
|
return false;
|
|
}
|
|
mCursorSuppliedFromOutside = true;
|
|
mCursor = cursor;
|
|
mRawContactEntitlesInfoCallback = rawContactEntitlesInfoCallback;
|
|
if (!initInterMainPart()) {
|
|
return false;
|
|
}
|
|
return initInterLastPart();
|
|
}
|
|
|
|
private boolean initInterFirstPart(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 initInterCursorCreationPart(
|
|
final Uri contentUri, final String[] projection,
|
|
final String selection, final String[] selectionArgs, final 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 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(Data.CONTACT_ID);
|
|
if (mIdColumn < 0) {
|
|
mIdColumn = mCursor.getColumnIndex(Contacts._ID);
|
|
}
|
|
return mIdColumn >= 0;
|
|
}
|
|
|
|
private boolean initInterLastPart() {
|
|
mInitDone = true;
|
|
mTerminateCalled = false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return a vCard string.
|
|
*/
|
|
public String createOneEntry() {
|
|
return createOneEntry(null);
|
|
}
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public String createOneEntry(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.getLong(mIdColumn),
|
|
getEntityIteratorMethod);
|
|
if (!mCursor.moveToNext()) {
|
|
Log.e(LOG_TAG, "Cursor#moveToNext() returned false");
|
|
}
|
|
return vcard;
|
|
}
|
|
|
|
/**
|
|
* Class that store rawContactEntitlesUri and contactId
|
|
*/
|
|
public static class RawContactEntitlesInfo {
|
|
public final Uri rawContactEntitlesUri;
|
|
public final long contactId;
|
|
public RawContactEntitlesInfo(Uri rawContactEntitlesUri, long contactId) {
|
|
this.rawContactEntitlesUri = rawContactEntitlesUri;
|
|
this.contactId = contactId;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Listener for getting raw contact entitles info
|
|
*/
|
|
public interface RawContactEntitlesInfoCallback {
|
|
/**
|
|
* Callback to get RawContactEntitlesInfo from contact id
|
|
*
|
|
* @param contactId Contact id that you want to process.
|
|
* @return RawContactEntitlesInfo that ready to process.
|
|
*/
|
|
RawContactEntitlesInfo getRawContactEntitlesInfo(long contactId);
|
|
}
|
|
|
|
private String createOneEntryInternal(long contactId,
|
|
final 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 {
|
|
Uri uri = mContentUriForRawContactsEntity;
|
|
if (mRawContactEntitlesInfoCallback != null) {
|
|
RawContactEntitlesInfo rawContactEntitlesInfo =
|
|
mRawContactEntitlesInfoCallback.getRawContactEntitlesInfo(contactId);
|
|
uri = rawContactEntitlesInfo.rawContactEntitlesUri;
|
|
contactId = rawContactEntitlesInfo.contactId;
|
|
}
|
|
final String selection = Data.CONTACT_ID + "=?";
|
|
final String[] selectionArgs = new String[] {String.valueOf(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);
|
|
}
|
|
|
|
private VCardPhoneNumberTranslationCallback mPhoneTranslationCallback;
|
|
/**
|
|
* <p>
|
|
* Set a callback for phone number formatting. It will be called every time when this object
|
|
* receives a phone number for printing.
|
|
* </p>
|
|
* <p>
|
|
* 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.
|
|
* </p>
|
|
* <p>
|
|
* Caution: This interface will change. Please don't use without any strong reason.
|
|
* </p>
|
|
*/
|
|
public void setPhoneNumberTranslationCallback(VCardPhoneNumberTranslationCallback callback) {
|
|
mPhoneTranslationCallback = callback;
|
|
}
|
|
|
|
/**
|
|
* 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))
|
|
.appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE),
|
|
mPhoneTranslationCallback)
|
|
.appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE))
|
|
.appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE))
|
|
.appendSipAddresses(contentValuesListMap.get(SipAddress.CONTENT_ITEM_TYPE));
|
|
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NICKNAME_EXPORT) == 0) {
|
|
builder.appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EMAIL_EXPORT) == 0) {
|
|
builder.appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ADDRESS_EXPORT) == 0) {
|
|
builder.appendPostals(contentValuesListMap.get(
|
|
StructuredPostal.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_ORGANIZATION_EXPORT) == 0) {
|
|
builder.appendOrganizations(contentValuesListMap.get(
|
|
Organization.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_WEBSITES_EXPORT) == 0) {
|
|
builder.appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
|
|
builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_NOTES_EXPORT) == 0) {
|
|
builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE));
|
|
}
|
|
if ((mVCardType & VCardConfig.FLAG_REFRAIN_EVENTS_EXPORT) == 0) {
|
|
builder.appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE));
|
|
}
|
|
return builder.toString();
|
|
}
|
|
}
|
|
|
|
public void terminate() {
|
|
closeCursorIfAppropriate();
|
|
mTerminateCalled = true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void finalize() throws Throwable {
|
|
try {
|
|
if (!mTerminateCalled) {
|
|
Log.e(LOG_TAG, "finalized() is called before terminate() being called");
|
|
}
|
|
} finally {
|
|
super.finalize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @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;
|
|
}
|
|
}
|