Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b1f5a54a authored by Daisuke Miyakawa's avatar Daisuke Miyakawa Committed by Android (Google) Code Review
Browse files

Merge "VCard refactoring backport." into gingerbread

parents 32ec1ad1 69831d9d
Loading
Loading
Loading
Loading
+2 −3
Original line number Diff line number Diff line
@@ -27,7 +27,6 @@ import java.util.Map;
        new HashMap<Character, String>();

    static {
        // There's no logical mapping rule in Unicode. Sigh.
        sHalfWidthMap.put('\u3001', "\uFF64");
        sHalfWidthMap.put('\u3002', "\uFF61");
        sHalfWidthMap.put('\u300C', "\uFF62");
@@ -366,11 +365,11 @@ import java.util.Map;
    }

    /**
     * Return half-width version of that character if possible. Return null if not possible
     * Returns half-width version of that character if possible. Returns null if not possible
     * @param ch input character
     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
     */
    public static String tryGetHalfWidthText(char ch) {
    public static String tryGetHalfWidthText(final char ch) {
        if (sHalfWidthMap.containsKey(ch)) {
            return sHalfWidthMap.get(ch);
        } else {
+308 −107

File changed.

Preview size limit exceeded, changes collapsed.

+164 −80
Original line number Diff line number Diff line
@@ -19,16 +19,12 @@ 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.content.EntityIterator;
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;
@@ -41,6 +37,11 @@ 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.provider.ContactsContract.Contacts;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.RawContactsEntity;
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>
 * {@link VCardBuilder} 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 {
    public final 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 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(OutputStream outputStream) {
        public HandlerForOutputStream(final OutputStream outputStream) {
            mOutputStream = outputStream;
        }

        public boolean onInit(Context context) {
        public 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;
            }

@@ -235,11 +237,16 @@ public class VCardComposer {
                            "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.");
            }
        }

@@ -257,11 +264,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,54 +278,109 @@ 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) {
        this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors);
    public VCardComposer(Context context, int vcardType, String charset) {
        this(context, vcardType, charset, true);
    }

    /**
     * Construct for supporting call log entry vCard composing.
     * 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);
        mUsesShiftJis = VCardConfig.usesShiftJis(vcardType);
        mHandlerList = new ArrayList<OneEntryHandler>();

        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)) {
                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.");
                        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;
                } else {
                    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.");
                        Log.e(LOG_TAG,
                                "Career-specific SHIFT_JIS was not found. "
                                + "Use SHIFT_JIS as is.");
                        charset = SHIFT_JIS;
                    }
            mCharsetString = 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 {
            mCharsetString = UTF_8;
                    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()}.
     */
@@ -351,7 +412,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 +475,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 +498,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 +517,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.
@@ -466,12 +526,13 @@ public class VCardComposer {
        EntityIterator entityIterator = null;
        try {
            final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon()
                    // .appendQueryParameter("for_export_only", "1")
                    .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 some tests only
                // Please note that this branch is executed by unit tests only
                try {
                    entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null,
                            mContentResolver, uri, selection, selectionArgs, null);
@@ -527,23 +588,34 @@ public class VCardComposer {
            }
        }

        final VCardBuilder builder = new VCardBuilder(mVCardType);
        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));
        if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) {
            builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE));
        }
        builder.appendNotes(contentValuesListMap.get(Note.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) {
@@ -565,26 +637,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;
+218 −192

File changed.

Preview size limit exceeded, changes collapsed.

+36 −13
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ package android.pim.vcard;
public class VCardConstants {
    public static final String VERSION_V21 = "2.1";
    public static final String VERSION_V30 = "3.0";
    public static final String VERSION_V40 = "4.0";

    // The property names valid both in vCard 2.1 and 3.0.
    public static final String PROPERTY_BEGIN = "BEGIN";
@@ -38,25 +39,30 @@ public class VCardConstants {
    public static final String PROPERTY_PHOTO = "PHOTO";
    public static final String PROPERTY_LOGO = "LOGO";
    public static final String PROPERTY_URL = "URL";
    public static final String PROPERTY_BDAY = "BDAY";  // Birthday
    public static final String PROPERTY_BDAY = "BDAY";  // Birthday (3.0, 4.0)
    public static final String PROPERTY_BIRTH = "BIRTH";  // Place of birth (4.0)
    public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY";  // Date of marriage (4.0)
    public static final String PROPERTY_NAME = "NAME";  // (3.0, 4,0)
    public static final String PROPERTY_NICKNAME = "NICKNAME";  // (3.0, 4.0)
    public static final String PROPERTY_SORT_STRING = "SORT-STRING";  // (3.0, 4.0)
    public static final String PROPERTY_END = "END";

    // Valid property names not supported (not appropriately handled) by our vCard importer now.
    // Valid property names not supported (not appropriately handled) by our importer.
    // TODO: Should be removed from the view of memory efficiency?
    public static final String PROPERTY_REV = "REV";
    public static final String PROPERTY_AGENT = "AGENT";
    public static final String PROPERTY_AGENT = "AGENT";  // (3.0)
    public static final String PROPERTY_DDAY = "DDAY";  // Date of death (4.0)
    public static final String PROPERTY_DEATH = "DEATH";  // Place of death (4.0)

    // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file.
    public static final String PROPERTY_NAME = "NAME";
    public static final String PROPERTY_NICKNAME = "NICKNAME";
    public static final String PROPERTY_SORT_STRING = "SORT-STRING";
    
    // De-fact property values expressing phonetic names.
    public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME";
    public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME";
    public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME";

    // Properties both ContactsStruct in Eclair and de-fact vCard extensions
    // shown in http://en.wikipedia.org/wiki/VCard support are defined here.
    // Properties both ContactsStruct and de-fact vCard extensions
    // Shown in http://en.wikipedia.org/wiki/VCard support are defined here.
    public static final String PROPERTY_X_AIM = "X-AIM";
    public static final String PROPERTY_X_MSN = "X-MSN";
    public static final String PROPERTY_X_YAHOO = "X-YAHOO";
@@ -89,6 +95,9 @@ public class VCardConstants {
    public static final String PARAM_TYPE_VOICE = "VOICE";
    public static final String PARAM_TYPE_INTERNET = "INTERNET";

    public static final String PARAM_CHARSET = "CHARSET";
    public static final String PARAM_ENCODING = "ENCODING";

    // Abbreviation of "prefered" according to vCard 2.1 specification.
    // We interpret this value as "primary" property during import/export.
    //
@@ -109,6 +118,12 @@ public class VCardConstants {
    public static final String PARAM_TYPE_BBS = "BBS";
    public static final String PARAM_TYPE_VIDEO = "VIDEO";

    public static final String PARAM_ENCODING_7BIT = "7BIT";
    public static final String PARAM_ENCODING_8BIT = "8BIT";
    public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE";
    public static final String PARAM_ENCODING_BASE64 = "BASE64";  // Available in vCard 2.1
    public static final String PARAM_ENCODING_B = "B";  // Available in vCard 3.0

    // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1).
    // These types are basically encoded to "X-" parameters when composing vCard.
    // Parser passes these when "X-" is added to the parameter or not.
@@ -126,14 +141,15 @@ public class VCardConstants {
    public static final String PARAM_ADR_TYPE_DOM = "DOM";
    public static final String PARAM_ADR_TYPE_INTL = "INTL";

    public static final String PARAM_LANGUAGE = "LANGUAGE";

    // SORT-AS parameter introduced in vCard 4.0 (as of rev.13)
    public static final String PARAM_SORT_AS = "SORT-AS";

    // TYPE parameters not officially valid but used in some vCard exporter.
    // Do not use in composer side.
    public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY";

    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in
    // vCard 3.0.
    public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";

    public interface ImportOnly {
        public static final String PROPERTY_X_NICKNAME = "X-NICKNAME";
        // Some device emits this "X-" parameter for expressing Google Talk,
@@ -142,7 +158,14 @@ public class VCardConstants {
        public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK";
    }

    /* package */ static final int MAX_DATA_COLUMN = 15;
    //// Mainly for package constants.

    // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of
    // SORT-STRING invCard 3.0.
    /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N";

    // Used in unit test.
    public static final int MAX_DATA_COLUMN = 15;

    /* package */ static final int MAX_CHARACTER_NUMS_QP = 76;
    static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75;
Loading