- Make Contacts app use new vCard handling codes
- Enable "multiple vCard selection"

Internal issue id: 2030674
diff --git a/res/values/config.xml b/res/values/config.xml
index 11aec0f..38c3e53 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -43,10 +43,13 @@
     <!-- How long to vibrate (in msec), if dialer key vibration is enabled. -->
     <integer name="config_dialer_key_vibrate_duration">40</integer>
 
-    <!-- The type of VCard for export. Without specifying this, generic VCard will be emitted.
-     If you want to let the app emit VCard specific to some vendor (like DoCoMo),
-     please specify the type.-->
-    <string name="config_export_vcard_type"></string>
+    <!-- The type of vcard for improt. If the vcard importer cannot guess the exact type
+    of a vCard type, the improter uses this type. -->
+    <string name="config_import_vcard_type">default</string>
+
+    <!-- The type of VCard for export. If you want to let the app emit vCard which is
+    specific to some vendor (like DoCoMo), specify this type (e.g. "docomo") -->
+    <string name="config_export_vcard_type">default</string>
 
     <!-- Directory in which exported VCard file is stored -->
     <string name="config_export_dir">/sdcard</string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 050abf9..033d3b9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -697,11 +697,14 @@
     <!-- Action string for selecting SD Card for importing contacts -->
     <string name="import_from_sdcard">SD Card</string>
 
-    <!-- "Import all VCard files" -->
-    <string name="import_all_vcard_string">Import all VCard files</string>
+    <!-- "Import one vCard file" -->
+    <string name="import_one_vcard_string">Import one vCard file</string>
 
-    <!-- "Import one VCard file" -->
-    <string name="import_one_vcard_string">Import one VCard file</string>
+    <!-- "Import more than one vCard -->
+    <string name="import_multiple_vcard_string">Import multiple vCard files</string>
+
+    <!-- "Import all vCard files" -->
+    <string name="import_all_vcard_string">Import all vCard files</string>
 
     <!-- Dialog message shown when searching VCard data from SD Card -->
     <string name="searching_vcard_message">Searching for VCard data on VCard</string>
@@ -713,7 +716,7 @@
     <string name="scanning_sdcard_failed_message">Scanning SD Card failed</string>
 
     <!-- The failed reason: "I/O Error" -->
-    <string name="fail_reason_io_error">I/O Error</string>
+    <string name="fail_reason_io_error">I/O Error (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
 
     <!-- The failed reason: "Failed to parse VCard data" -->
     <string name="fail_reason_vcard_parse_error">Failed to parse VCard with some unexpected reason</string>
@@ -727,12 +730,20 @@
     <!-- The failed reason: "There is no valid VCard entry in the file(s)" -->
     <string name="fail_reason_no_vcard_entry">No valid VCard entry found for your selection</string>
 
+    <!-- The failed reason: "One or more files failed to be imported." -->
+    <string name="fail_reason_failed_to_read_files">One or more files failed to be imported.</string>
+
     <!-- Dialog title shown when a user is asked to select VCard file -->
     <string name="select_vcard_title">Select VCard file</string>
 
     <!-- Dialog message shown when a user is asked to choose VCard file -->
     <string name="select_vcard_message">Please select a VCard file to import</string>
 
+    <!-- The message shown while reading a vCard file/entry. The first argument is like 
+    "Reading VCard" or "Reading VCard files" and the second is the display name of the current 
+    data being parsed -->
+    <string name="progress_shower_message">%s\n%s</string>
+
     <!-- Dialog title shown when reading VCard data -->
     <string name="reading_vcard_title">Reading VCard</string>
 
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index b1dd11a..725c65b 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -50,10 +50,12 @@
 import java.io.IOException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 import java.util.Vector;
 
@@ -91,7 +93,6 @@
 
     private ProgressDialog mProgressDialog;
     private Handler mHandler = new Handler();
-    private boolean mLastNameComesBeforeFirstName;
 
     private class CancelListener
         implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
@@ -129,13 +130,16 @@
 
     private class VCardReadThread extends Thread
             implements DialogInterface.OnCancelListener {
-        private String mCanonicalPath;
-        private List<VCardFile> mVCardFileList;
         private ContentResolver mResolver;
         private VCardParser_V21 mVCardParser;
         private boolean mCanceled;
         private PowerManager.WakeLock mWakeLock;
+        private String mCanonicalPath;
 
+        // For reading multiple files.
+        private List<VCardFile> mVCardFileList;
+        private List<String> mErrorFileNameList;
+        
         public VCardReadThread(String canonicalPath) {
             mCanonicalPath = canonicalPath;
             mVCardFileList = null;
@@ -145,6 +149,7 @@
         public VCardReadThread(List<VCardFile> vcardFileList) {
             mCanonicalPath = null;
             mVCardFileList = vcardFileList;
+            mErrorFileNameList = new ArrayList<String>();
             init();
         }
 
@@ -167,12 +172,13 @@
 
         @Override
         public void run() {
+            boolean shouldCallFinish = true;
             mWakeLock.acquire();
             // Some malicious vCard data may make this thread broken
             // (e.g. OutOfMemoryError).
             // Even in such cases, some should be done.
             try {
-                if (mCanonicalPath != null) {
+                if (mCanonicalPath != null) {  // Read one file
                     mProgressDialog.setProgressNumberFormat("");
                     mProgressDialog.setProgress(0);
 
@@ -190,13 +196,13 @@
                     boolean result;
                     try {
                         result = readOneVCardFile(mCanonicalPath,
-                                VCardConfig.DEFAULT_CHARSET, builderCollection, null, true);
+                                VCardConfig.DEFAULT_CHARSET, builderCollection, null, true, null);
                     } catch (VCardNestedException e) {
                         try {
                             // Assume that VCardSourceDetector was able to detect the source.
                             // Try again with the detector.
                             result = readOneVCardFile(mCanonicalPath,
-                                    VCardConfig.DEFAULT_CHARSET, counter, detector, false);
+                                    VCardConfig.DEFAULT_CHARSET, counter, detector, false, null);
                         } catch (VCardNestedException e2) {
                             result = false;
                             Log.e(LOG_TAG, "Must not reach here. " + e2);
@@ -208,6 +214,7 @@
                                 time + " ms");
                     }
                     if (!result) {
+                        shouldCallFinish = false;
                         return;
                     }
 
@@ -216,12 +223,13 @@
                     mProgressDialog.setIndeterminate(false);
                     mProgressDialog.setMax(counter.getCount());
                     String charset = detector.getEstimatedCharset();
-                    doActuallyReadOneVCard(charset, true, detector);
-                } else {
+                    doActuallyReadOneVCard(mCanonicalPath, charset, true, detector, null);
+                } else {  // Read multiple files.
                     mProgressDialog.setProgressNumberFormat(
                             getString(R.string.reading_vcard_files));
                     mProgressDialog.setMax(mVCardFileList.size());
                     mProgressDialog.setProgress(0);
+                    
                     for (VCardFile vcardFile : mVCardFileList) {
                         if (mCanceled) {
                             return;
@@ -231,60 +239,80 @@
                         VCardSourceDetector detector = new VCardSourceDetector();
                         try {
                             if (!readOneVCardFile(canonicalPath, VCardConfig.DEFAULT_CHARSET,
-                                    detector, null, true)) {
+                                    detector, null, true, mErrorFileNameList)) {
                                 continue;
                             }
                         } catch (VCardNestedException e) {
                             // Assume that VCardSourceDetector was able to detect the source.
                         }
                         String charset = detector.getEstimatedCharset();
-                        doActuallyReadOneVCard(charset, false, detector);
+                        doActuallyReadOneVCard(canonicalPath,
+                                charset, false, detector, mErrorFileNameList);
                         mProgressDialog.incrementProgressBy(1);
                     }
                 }
             } finally {
                 mWakeLock.release();
                 mProgressDialog.dismiss();
-                finish();
+                // finish() is called via ErrorDisplayer() on failure.
+                if (shouldCallFinish) {
+                    if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
+                        finish();
+                    } else {
+                        StringBuilder builder = new StringBuilder();
+                        boolean first = true;
+                        for (String fileName : mErrorFileNameList) {
+                            if (first) {
+                                first = false;
+                            } else {
+                                builder.append(", ");
+                            }
+                            builder.append(fileName);
+                        }
+                        
+                        mHandler.post(new ErrorDisplayer(
+                                getString(R.string.fail_reason_failed_to_read_files, 
+                                        builder.toString())));
+                    }
+                }
             }
         }
 
-        private void doActuallyReadOneVCard(String charset, boolean doIncrementProgress,
-                VCardSourceDetector detector) {
+        private boolean doActuallyReadOneVCard(String canonicalPath,
+                String charset, boolean showEntryParseProgress,
+                VCardSourceDetector detector, List<String> errorFileNameList) {
             final Context context = ImportVCardActivity.this;
             VCardDataBuilder builder;
-            int nameOrderType =
-                (mLastNameComesBeforeFirstName ? 
-                        VCardConfig.NAME_ORDER_TYPE_JAPANESE :
-                            VCardConfig.NAME_ORDER_TYPE_ENGLISH);
+            final String currentLanguage = Locale.getDefault().getLanguage();
+            int vcardType = VCardConfig.getVCardTypeFromString(
+                    context.getString(R.string.config_import_vcard_type));
             if (charset != null) {
-                builder = new VCardDataBuilder(charset,
-                        charset,
-                        false,
-                        nameOrderType);
+                builder = new VCardDataBuilder(charset, charset, false, vcardType);
             } else {
                 charset = VCardConfig.DEFAULT_CHARSET;
-                builder = new VCardDataBuilder(null,
-                        null,
-                        false,
-                        nameOrderType);
+                builder = new VCardDataBuilder(null, null, false, vcardType);
             }
             builder.addEntryHandler(new EntryCommitter(mResolver));
-            builder.addEntryHandler(new ProgressShower(mProgressDialog,
-                    context.getString(R.string.reading_vcard_message),
-                    mHandler,
-                    doIncrementProgress));
+            if (showEntryParseProgress) {
+                builder.addEntryHandler(new ProgressShower(mProgressDialog,
+                        context.getString(R.string.reading_vcard_message),
+                        ImportVCardActivity.this,
+                        mHandler));
+            }
 
             try {
-                readOneVCardFile(mCanonicalPath, charset, builder, detector, false);
+                if (!readOneVCardFile(canonicalPath, charset, builder, detector, false, null)) {
+                    return false;
+                }
             } catch (VCardNestedException e) {
                 Log.e(LOG_TAG, "Never reach here.");
             }
+            return true;
         }
 
         private boolean readOneVCardFile(String canonicalPath, String charset,
                 VCardBuilder builder, VCardSourceDetector detector,
-                boolean throwNestedException)
+                boolean throwNestedException, List<String> errorFileNameList)
                 throws VCardNestedException {
             FileInputStream is;
             try {
@@ -319,22 +347,34 @@
 
                 mProgressDialog.dismiss();
 
-                mHandler.post(new ErrorDisplayer(
-                        getString(R.string.fail_reason_io_error) +
-                        " (" + e.getMessage() + ")"));
+                if (errorFileNameList != null) {
+                    errorFileNameList.add(canonicalPath);
+                } else {
+                    mHandler.post(new ErrorDisplayer(
+                            getString(R.string.fail_reason_io_error,
+                                    e.getMessage())));                    
+                }
                 return false;
             } catch (VCardNotSupportedException e) {
                 if ((e instanceof VCardNestedException) && throwNestedException) {
                     throw (VCardNestedException)e;
                 }
-                mHandler.post(new ErrorDisplayer(
-                        getString(R.string.fail_reason_vcard_not_supported_error) +
-                        " (" + e.getMessage() + ")"));
+                if (errorFileNameList != null) {
+                    errorFileNameList.add(canonicalPath);
+                } else {
+                    mHandler.post(new ErrorDisplayer(
+                            getString(R.string.fail_reason_vcard_not_supported_error) +
+                            " (" + e.getMessage() + ")"));
+                }
                 return false;
             } catch (VCardException e) {
-                mHandler.post(new ErrorDisplayer(
-                        getString(R.string.fail_reason_vcard_parse_error) +
-                        " (" + e.getMessage() + ")"));
+                if (errorFileNameList != null) {
+                    errorFileNameList.add(canonicalPath);
+                } else {
+                    mHandler.post(new ErrorDisplayer(
+                            getString(R.string.fail_reason_vcard_parse_error) +
+                            " (" + e.getMessage() + ")"));
+                }
                 return false;
             }
             return true;
@@ -350,9 +390,11 @@
 
     private class ImportTypeSelectedListener implements
             DialogInterface.OnClickListener {
-        public static final int IMPORT_ALL = 0;
-        public static final int IMPORT_ONE = 1;
-
+        public static final int IMPORT_ONE = 0;
+        public static final int IMPORT_MULTIPLE = 1;
+        public static final int IMPORT_ALL = 2;
+        public static final int IMPORT_TYPE_SIZE = 3;
+        
         private List<VCardFile> mVCardFileList;
         private int mCurrentIndex;
 
@@ -362,10 +404,15 @@
 
         public void onClick(DialogInterface dialog, int which) {
             if (which == DialogInterface.BUTTON_POSITIVE) {
-                if (mCurrentIndex == IMPORT_ALL) {
-                    importAllVCardFromSDCard(mVCardFileList);
-                } else {
-                    showVCardFileSelectDialog(mVCardFileList);
+                switch (mCurrentIndex) {
+                case IMPORT_ALL:
+                    importMultipleVCardFromSDCard(mVCardFileList);
+                    break;
+                case IMPORT_MULTIPLE:
+                    showVCardFileSelectDialog(mVCardFileList, true);
+                    break;
+                default:
+                    showVCardFileSelectDialog(mVCardFileList, false);
                 }
             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
                 finish();
@@ -375,23 +422,58 @@
         }
     }
 
-    private class VCardSelectedListener implements DialogInterface.OnClickListener {
+    
+    private class VCardSelectedListener implements
+            DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
         private List<VCardFile> mVCardFileList;
         private int mCurrentIndex;
+        private Set<Integer> mSelectedIndexSet;
 
-        public VCardSelectedListener(List<VCardFile> vcardFileList) {
+        public VCardSelectedListener(
+                List<VCardFile> vcardFileList, boolean multipleSelect) {
             mVCardFileList = vcardFileList;
             mCurrentIndex = 0;
+            if (multipleSelect) {
+                mSelectedIndexSet = new HashSet<Integer>();
+            }
         }
 
         public void onClick(DialogInterface dialog, int which) {
             if (which == DialogInterface.BUTTON_POSITIVE) {
-                importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+                if (mSelectedIndexSet != null) {
+                    List<VCardFile> selectedVCardFileList = new ArrayList<VCardFile>();
+                    int size = mVCardFileList.size();
+                    // We'd like to sort the files by its index, so we do not use Set iterator. 
+                    for (int i = 0; i < size; i++) {
+                        if (mSelectedIndexSet.contains(i)) {
+                            selectedVCardFileList.add(mVCardFileList.get(i));
+                        }
+                    }
+                    importMultipleVCardFromSDCard(selectedVCardFileList);
+                } else {
+                    importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+                }
             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
                 finish();
             } else {
                 // Some file is selected.
                 mCurrentIndex = which;
+                if (mSelectedIndexSet != null) {
+                    if (mSelectedIndexSet.contains(which)) {
+                        mSelectedIndexSet.remove(which);
+                    } else {
+                        mSelectedIndexSet.add(which);
+                    }
+                }
+            }
+        }
+
+        public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+            if (mSelectedIndexSet == null || (mSelectedIndexSet.contains(which) == isChecked)) {
+                Log.e(LOG_TAG, String.format("Inconsist state in index %d (%s)", which,
+                        mVCardFileList.get(which).getCanonicalPath()));
+            } else {
+                onClick(dialog, which);
             }
         }
     }
@@ -484,14 +566,15 @@
                             return;
                         } else if (context.getResources().getBoolean(
                                 R.bool.config_import_all_vcard_from_sdcard_automatically)) {
-                            importAllVCardFromSDCard(mVCardFiles);
+                            importMultipleVCardFromSDCard(mVCardFiles);
                         } else if (size == 1) {
                             importOneVCardFromSDCard(mVCardFiles.get(0).getCanonicalPath());
                         } else if (context.getResources().getBoolean(
                                 R.bool.config_allow_users_select_all_vcard_import)) {
                             showSelectImportTypeDialog(mVCardFiles);
                         } else {
-                            showVCardFileSelectDialog(mVCardFiles);
+                            // Let a user to select one vCard file.
+                            showVCardFileSelectDialog(mVCardFiles, false);
                         }
                     }
                 });
@@ -539,13 +622,13 @@
     }
 
     
-    private void importOneVCardFromSDCard(String canonicalPath) {
+    private void importOneVCardFromSDCard(final String canonicalPath) {
         VCardReadThread thread = new VCardReadThread(canonicalPath);
         showReadingVCardDialog(thread);
         thread.start();
     }
 
-    private void importAllVCardFromSDCard(List<VCardFile> vcardFileList) {
+    private void importMultipleVCardFromSDCard(final List<VCardFile> vcardFileList) {
         VCardReadThread thread = new VCardReadThread(vcardFileList);
         showReadingVCardDialog(thread);
         thread.start();
@@ -561,20 +644,23 @@
                 .setOnCancelListener(mCancelListener)
                 .setNegativeButton(android.R.string.cancel, mCancelListener);
 
-        String[] items = new String[2];
-        items[ImportTypeSelectedListener.IMPORT_ALL] =
-            getString(R.string.import_all_vcard_string);
+        String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
         items[ImportTypeSelectedListener.IMPORT_ONE] =
             getString(R.string.import_one_vcard_string);
+        items[ImportTypeSelectedListener.IMPORT_MULTIPLE] =
+            getString(R.string.import_multiple_vcard_string);
+        items[ImportTypeSelectedListener.IMPORT_ALL] =
+            getString(R.string.import_all_vcard_string);
         builder.setSingleChoiceItems(items,
-                ImportTypeSelectedListener.IMPORT_ALL, listener);
+                ImportTypeSelectedListener.IMPORT_ONE, listener);
         builder.show();
     }
 
-    private void showVCardFileSelectDialog(List<VCardFile> vcardFileList) {
+    private void showVCardFileSelectDialog(
+            List<VCardFile> vcardFileList, boolean multipleSelect) {
         int size = vcardFileList.size();
-        DialogInterface.OnClickListener listener =
-            new VCardSelectedListener(vcardFileList);
+        VCardSelectedListener listener =
+            new VCardSelectedListener(vcardFileList, multipleSelect);
         AlertDialog.Builder builder =
             new AlertDialog.Builder(this)
                 .setTitle(R.string.select_vcard_title)
@@ -600,7 +686,11 @@
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             items[i] = stringBuilder;
         }
-        builder.setSingleChoiceItems(items, 0, listener);
+        if (multipleSelect) {
+            builder.setMultiChoiceItems(items, (boolean[])null, listener);
+        } else {
+            builder.setSingleChoiceItems(items, 0, listener);
+        }
         builder.show();
     }
 
@@ -619,9 +709,6 @@
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
 
-        mLastNameComesBeforeFirstName = getResources().getBoolean(
-                com.android.internal.R.bool.config_lastname_comes_before_firstname);
-
         startImportVCardFromSdCard();
     }
 
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
index 9498569..c1a2493 100644
--- a/src/com/android/contacts/ProgressShower.java
+++ b/src/com/android/contacts/ProgressShower.java
@@ -16,6 +16,7 @@
 package com.android.contacts;
 
 import android.app.ProgressDialog;
+import android.content.Context;
 import android.os.Handler;
 import android.pim.vcard.ContactStruct;
 import android.pim.vcard.EntryHandler;
@@ -25,10 +26,10 @@
 public class ProgressShower implements EntryHandler {
     public static final String LOG_TAG = "vcard.ProgressShower"; 
 
+    private final Context mContext;
     private final Handler mHandler;
     private final ProgressDialog mProgressDialog;
     private final String mProgressMessage;
-    private final boolean mIncrementProgress;
 
     private long mTime;
     
@@ -40,24 +41,25 @@
         }
         
         public void run() {
-            mProgressDialog.setMessage(mProgressMessage + "\n" + 
-                    mContact.displayString());
-            if (mIncrementProgress) {
-                mProgressDialog.incrementProgressBy(1);
-            }
+            mProgressDialog.setMessage( mProgressMessage + "\n" + 
+                    mContact.getDisplayName());
+            mProgressDialog.incrementProgressBy(1);
         }
     }
     
     public ProgressShower(ProgressDialog progressDialog,
             String progressMessage,
-            Handler handler, 
-            boolean incrementProgress) {
+            Context context,
+            Handler handler) {
+        mContext = context;
         mHandler = handler;
         mProgressDialog = progressDialog;
         mProgressMessage = progressMessage;
-        mIncrementProgress = incrementProgress;
     }
-    
+
+    public void onParsingStart() {
+    }
+
     public void onEntryCreated(ContactStruct contactStruct) {
         long start = System.currentTimeMillis();
         
@@ -66,8 +68,9 @@
                 if (mHandler != null) {
                     mHandler.post(new ShowProgressRunnable(contactStruct));
                 } else {
-                    mProgressDialog.setMessage(mProgressMessage + "\n" + 
-                            contactStruct.displayString());
+                    mProgressDialog.setMessage(mContext.getString(R.string.progress_shower_message,
+                            mProgressMessage, 
+                            contactStruct.getDisplayName()));
                 }
             }
         }
@@ -75,10 +78,10 @@
         mTime += System.currentTimeMillis() - start;
     }
 
-    public void onFinal() {
+    public void onParsingEnd() {
         if (VCardConfig.showPerformanceLog()) {
             Log.d(LOG_TAG,
-                    String.format("Time to progress a dialog: %ld ms", mTime));
+                    String.format("Time to progress a dialog: %d ms", mTime));
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/contacts/VCardExporter.java b/src/com/android/contacts/VCardExporter.java
index 2b3c4eb..2e9ab2c 100644
--- a/src/com/android/contacts/VCardExporter.java
+++ b/src/com/android/contacts/VCardExporter.java
@@ -17,35 +17,20 @@
 
 import android.app.AlertDialog;
 import android.app.ProgressDialog;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.Resources;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
 import android.os.Handler;
 import android.os.PowerManager;
-import android.provider.Contacts;
-import android.provider.Contacts.People;
-import android.syncml.pim.PropertyNode;
-import android.telephony.TelephonyManager;
+import android.pim.vcard.VCardComposer;
 import android.text.TextUtils;
-import android.util.CharsetUtils;
 import android.util.Log;
 
-import java.io.BufferedWriter;
 import java.io.File;
 import java.io.FileNotFoundException;
 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.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 
 public class VCardExporter {
@@ -59,7 +44,7 @@
     private final int mFileIndexMinimum;
     private final int mFileIndexMaximum;
     private final String mFileNameExtension;
-    private final String mVCardType;
+    private final String mVCardTypeStr;
     private final Set<String> mExtensionsToConsider;
 
     private Context mParentContext;
@@ -114,7 +99,7 @@
         @Override
         public void run() {
             mWakeLock.acquire();
-            VCardExporterImpl exporterImpl = null;
+            VCardComposer composer = null;
             try {
                 OutputStream outputStream = null;
                 try {
@@ -126,43 +111,41 @@
                     return;
                 }
 
-                TelephonyManager telephonyManager =
-                    (TelephonyManager)mParentContext.getSystemService(
-                            Context.TELEPHONY_SERVICE);
+                composer = new VCardComposer(mParentContext, mVCardTypeStr, true);
+                // composer = new VCardComposer(mParentContext,
+                // VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8, true);
+                composer.addHandler(composer.new HandlerForOutputStream(outputStream));
 
-                exporterImpl = new VCardExporterImpl(mParentContext.getContentResolver(),
-                        outputStream, mVCardType);
-
-                if (!exporterImpl.init()) {
+                if (!composer.init()) {
                     String reason = getString(R.string.fail_reason_could_not_initialize_exporter,
-                            exporterImpl.getErrorReason());
+                            composer.getErrorReason());
                     mParentHandler.post(new ErrorMessageDisplayRunnable(reason));
                     return;
                 }
 
-                int size = exporterImpl.getCount();
+                int size = composer.getCount();
 
                 mProgressDialog.setProgressNumberFormat(
                         getString(R.string.exporting_contact_list_progress));
                 mProgressDialog.setMax(size);
                 mProgressDialog.setProgress(0);
 
-                while (!exporterImpl.isAfterLast()) {
+                while (!composer.isAfterLast()) {
                     if (mCanceled) {
                         return;
                     }
-                    if (!exporterImpl.exportOneContactData()) {
+                    if (!composer.createOneEntry()) {
                         Log.e(LOG_TAG, "Failed to read a contact.");
                         String reason = getString(R.string.fail_reason_error_occurred_during_export,
-                                exporterImpl.getErrorReason());
+                                composer.getErrorReason());
                         mParentHandler.post(new ErrorMessageDisplayRunnable(reason));
                         return;
                     }
                     mProgressDialog.incrementProgressBy(1);
                 }
             } finally {
-                if (exporterImpl != null) {
-                    exporterImpl.terminate();
+                if (composer != null) {
+                    composer.terminate();
                 }
                 mWakeLock.release();
                 mProgressDialog.dismiss();
@@ -192,7 +175,7 @@
         mFileNamePrefix = getString(R.string.config_export_file_prefix);
         mFileNameSuffix = getString(R.string.config_export_file_suffix);
         mFileNameExtension = getString(R.string.config_export_file_extension);
-        mVCardType = getString(R.string.config_export_vcard_type);
+        mVCardTypeStr = getString(R.string.config_export_vcard_type);
 
         mExtensionsToConsider = new HashSet<String>();
         mExtensionsToConsider.add(mFileNameExtension);
@@ -329,1346 +312,4 @@
     private String getString(int resId) {
         return mParentContext.getString(resId);
     }
-}
-
-// TODO: This class should be splitted into two parts; exporter part and composer part.
-class VCardExporterImpl {
-    private static final String LOG_TAG = "VCardExporterImpl";
-
-    /* Type of exporting VCard. */
-    // General VCard. Use UTF-8 and do not care vendor specific things.
-    public static int VCARD_TYPE_GENERIC = 0;
-    // VCard format used in DoCoMo. Shift_Jis is used as charset.
-    public static int VCARD_TYPE_DOCOMO = 1;
-    public static final String VCARD_TYPE_STRING_DOCOMO = "docomo";
-
-    private static final String VCARD_PROPERTY_ADR = "ADR";
-    private static final String VCARD_PROPERTY_BEGIN = "BEGIN";
-    private static final String VCARD_PROPERTY_EMAIL = "EMAIL";
-    private static final String VCARD_PROPERTY_END = "END";
-    private static final String VCARD_PROPERTY_NAME = "N";
-    private static final String VCARD_PROPERTY_NOTE = "NOTE";
-    private static final String VCARD_PROPERTY_ORG = "ORG";
-    private static final String VCARD_PROPERTY_SOUND = "SOUND";
-    private static final String VCARD_PROPERTY_TEL = "TEL";
-    private static final String VCARD_PROPERTY_TITLE = "TITLE";
-    private static final String VCARD_PROPERTY_PHOTO = "PHOTO";
-    private static final String VCARD_PROPERTY_VERSION = "VERSION";
-    private static final String VCARD_PROPERTY_BDAY = "BDAY";
-    private static final String VCARD_PROPERTY_URL = "URL";
-
-    // Properties for DoCoMo vCard.
-    private static final String VCARD_PROPERTY_X_CLASS = "X-CLASS";
-    private static final String VCARD_PROPERTY_X_REDUCTION = "X-REDUCTION";
-    private static final String VCARD_PROPERTY_X_NO = "X-NO";
-    private static final String VCARD_PROPERTY_X_DCM_HMN_MODE = "X-DCM-HMN-MODE";
-
-    private static final String VCARD_DATA_VCARD = "VCARD";
-    private static final String VCARD_DATA_VERSION_V21 = "2.1";
-    private static final String VCARD_DATA_PUBLIC = "PUBLIC";
-
-    private static final String VCARD_ATTR_SEPARATOR = ";";
-    private static final String VCARD_COL_SEPARATOR = "\r\n";
-    private static final String VCARD_DATA_SEPARATOR = ":";
-    private static final String VCARD_ITEM_SEPARATOR = ";";
-    private static final String VCARD_WS = " ";
-
-    private static final String VCARD_ATTR_VOICE = "VOICE";
-    private static final String VCARD_ATTR_CELL = "CELL";
-    private static final String VCARD_ATTR_WORK = "WORK";
-    private static final String VCARD_ATTR_HOME = "HOME";
-    private static final String VCARD_ATTR_FAX = "FAX";
-    private static final String VCARD_ATTR_INTERNET = "INTERNET";
-    private static final String VCARD_ATTR_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE";
-    private static final String VCARD_ATTR_ENCODING_BASE64_V21 = "ENCODING=BASE64";
-    // This is just a reminder: in VCard 3.0, do not use the string "BASE64"
-    @SuppressWarnings("unused")
-    private static final String VCARD_ATTR_ENCODING_BASE64_V30 = "ENCODING=b";
-
-    // DoCoMo specific attribute.Used with "SOUND" property.
-    private static final String VCARD_ATTR_X_IRMC_N = "X-IRMC-N";
-
-    private static final String SHIFT_JIS = "SHIFT_JIS";
-
-    private Cursor mCursor;
-    private int mIdColumn;
-    private int mNameColumn;
-    private int mNotesColumn;
-    private int mPhoneticNameColumn;
-    private ContentResolver mContentResolver;
-
-    private int mVCardType;
-    private String mCharsetString;
-    private static String mVCardAttributeCharset;
-    private OutputStream mOutputStream;  // mWriter will close this.
-    private Writer mWriter;
-    private boolean mTerminateIsCalled;
-
-    private String mErrorReason = "No error";
-
-    /**
-     * @param resolver
-     * @param outputStream close() must not be called outside.
-     * @param vcardType
-     */
-    public VCardExporterImpl(ContentResolver resolver, OutputStream outputStream, int vcardType) {
-        mContentResolver = resolver;
-        mOutputStream = outputStream;
-
-        mVCardType = vcardType;
-        if (vcardType == VCARD_TYPE_DOCOMO) {
-            mCharsetString = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name();
-            mVCardAttributeCharset = "CHARSET=" + SHIFT_JIS;
-        } else {
-            mCharsetString = "UTF-8";
-            mVCardAttributeCharset = "CHARSET=UTF-8";
-        }
-    }
-
-    public VCardExporterImpl(ContentResolver resolver, OutputStream outputStream, String vcardType) {
-        this(resolver, outputStream,
-                (vcardType.equalsIgnoreCase(VCARD_TYPE_STRING_DOCOMO) ?
-                        VCARD_TYPE_DOCOMO : VCARD_TYPE_GENERIC));
-    }
-
-    /**
-     * @return Returns true when initialization is successful and all the other methods are
-     * available. Returns false otherwise.
-     */
-    public boolean init() {
-        try {
-            mWriter = new BufferedWriter(
-                    new OutputStreamWriter(mOutputStream, mCharsetString));
-        } catch (UnsupportedEncodingException e1) {
-            Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString);
-            mErrorReason = "Encoding is not supported (usually this does not happen!): " +
-                mCharsetString;
-            return false;
-        }
-
-        final String[] projection = new String[] {
-                People._ID,
-                People.NAME,
-                People.NOTES,
-                People.PHONETIC_NAME,
-        };
-
-        mCursor = mContentResolver.query(People.CONTENT_URI, projection, null, null, null);
-        if (mCursor == null || !mCursor.moveToFirst()) {
-            if (mCursor != null) {
-                try {
-                    mCursor.close();
-                } catch (SQLiteException e) {
-                }
-                mCursor = null;
-            }
-            mErrorReason = "Getting database information failed.";
-            return false;
-        }
-
-        mIdColumn = mCursor.getColumnIndex(People._ID);
-        mNameColumn = mCursor.getColumnIndex(People.NAME);
-        mNotesColumn = mCursor.getColumnIndex(People.NOTES);
-        mPhoneticNameColumn = mCursor.getColumnIndex(People.PHONETIC_NAME);
-
-        if (mVCardType == VCARD_TYPE_DOCOMO) {
-            try {
-                mWriter.write(convertContactToVCard(new ContactData()));
-            } catch (IOException e) {
-                Log.e(LOG_TAG, "IOException occurred during exportOneContactData: " +
-                        e.getMessage());
-                mErrorReason = "IOException occurred: " + e.getMessage();
-            }
-        }
-
-        return true;
-    }
-
-    @Override
-    public void finalize() {
-        if (!mTerminateIsCalled) {
-            terminate();
-        }
-    }
-
-    public void terminate() {
-        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) {
-                    try {
-                        ((FileOutputStream)mOutputStream).getFD().sync();
-                    } catch (IOException e) {
-                    }
-                }
-                mWriter.close();
-            } catch (IOException e) {
-            }
-        }
-        if (mCursor != null) {
-            try {
-                mCursor.close();
-            } catch (SQLiteException e) {
-            }
-            mCursor = null;
-        }
-    }
-
-    public int getCount() {
-        if (mCursor == null) {
-            return 0;
-        }
-        return mCursor.getCount();
-    }
-
-    public boolean isAfterLast() {
-        if (mCursor == null) {
-            return false;
-        }
-        return mCursor.isAfterLast();
-    }
-
-    public boolean exportOneContactData() {
-        if (mCursor == null || mCursor.isAfterLast()) {
-            mErrorReason = "Not initialized or database has some problem.";
-            return false;
-        }
-        String name = null;
-        try {
-            ContactData contactData = new ContactData();
-            int personId = mCursor.getInt(mIdColumn);
-            name = contactData.mName = mCursor.getString(mNameColumn);
-            contactData.mNote = mCursor.getString(mNotesColumn);
-            contactData.mPhoneticName = mCursor.getString(mPhoneticNameColumn);
-
-            readAllPhones(contactData, personId);
-            readAllAddresses(contactData, personId);
-            readAllOrgs(contactData, personId);
-            readAllPhotos(contactData, personId);
-            readAllExtensions(contactData, personId);
-
-            mCursor.moveToNext();
-
-            String vcardString = convertContactToVCard(contactData);
-            try {
-                mWriter.write(vcardString);
-            } catch (IOException e) {
-                Log.e(LOG_TAG, "IOException occurred during exportOneContactData: " +
-                        e.getMessage());
-                mErrorReason = "IOException occurred: " + 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: " + name);
-            System.gc();
-        }
-
-        return true;
-    }
-
-    /**
-     * @return Return the error reason if possible.
-     */
-    public String getErrorReason() {
-        return mErrorReason;
-    }
-
-    private void readAllPhones(ContactData contact, int personId) {
-        final String[] projection = new String[] {
-                Contacts.Phones.TYPE,
-                Contacts.Phones.LABEL,
-                Contacts.Phones.NUMBER,
-                Contacts.Phones.LABEL,
-        };
-        String selection = String.format("%s=%d", Contacts.Phones.PERSON_ID, personId);
-        Cursor cursor = null;
-        try {
-            cursor = mContentResolver.query(Contacts.Phones.CONTENT_URI,
-                    projection, selection, null, null);
-            if ((cursor != null) && (cursor.moveToFirst())) {
-                int typeColumn = cursor.getColumnIndex(Contacts.Phones.TYPE);
-                int labelColumn = cursor.getColumnIndex(Contacts.Phones.LABEL);
-                int numberColumn = cursor.getColumnIndex(Contacts.Phones.NUMBER);
-                do {
-                    TelData telData = new TelData(cursor.getInt(typeColumn),
-                            cursor.getString(labelColumn), cursor.getString(numberColumn));
-                    contact.mTel.add(telData);
-                } while (cursor.moveToNext());
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private void readAllPhotos(ContactData contact, int personId) {
-        final String[] projection = new String[] {
-                Contacts.Photos.DATA,
-        };
-        String selection = String.format("%s=%d", Contacts.Photos.PERSON_ID, personId);
-        Cursor cursor = null;
-        try {
-            cursor = mContentResolver.query(Contacts.Photos.CONTENT_URI,
-                    projection, selection, null, null);
-            if ((cursor != null) && (cursor.moveToFirst())) {
-                int dataColumn = cursor.getColumnIndex(Contacts.Photos.DATA);
-
-                byte[] data;
-                do {
-                    data = cursor.getBlob(dataColumn);
-                    // Use some heuristics for guessing the format of the image.
-                    if (data != null && data.length > 0) {
-                        if (data.length >= 3 &&
-                                data[0] == 'G' && data[1] == 'I' && data[2] == 'F') {
-                            contact.mPhotoType = "GIF";
-                        } else if (data.length >= 4 &&
-                                data[0] == (byte)0x89 && data[1] == 'P' && data[2] == 'N' &&
-                                data[3] == 'G') {
-                            // Note: vCard 2.1 officially does not support PNG, but we may have it
-                            // and using X- word like "X-PNG" may not let importers know it is
-                            // PNG. So we use the String "PNG" as is...
-                            contact.mPhotoType = "PNG";
-                        } else if (data.length >= 2 &&
-                                data[0] == (byte)0xff && data[1] == (byte)0xd8) {
-                            contact.mPhotoType = "JPEG";
-                        } else {
-                            // TODO: vCard specification requires the other formats like TIFF...
-                            Log.d(LOG_TAG, "Unknown photo type. Ignore.");
-                            continue;
-                        }
-                    }
-                    String photoData = encodeBase64(data);
-                    if (photoData.length() > 0) {
-                        contact.mPhoto = photoData;
-                    }
-                } while (cursor.moveToNext());
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private void readAllAddresses(ContactData contact, int personId) {
-        final String[] projection = new String[] {
-                Contacts.ContactMethods.TYPE,
-                Contacts.ContactMethods.LABEL,
-                Contacts.ContactMethods.DATA,
-                Contacts.ContactMethods.KIND,
-        };
-        String selection = String.format("%s=%d AND %s IN (1,2)",
-                Contacts.ContactMethods.PERSON_ID, personId, Contacts.ContactMethods.KIND);
-        Cursor cursor = null;
-        try {
-            cursor = mContentResolver.query(Contacts.ContactMethods.CONTENT_URI,
-                    projection, selection, null, null);
-            if ((cursor != null) && (cursor.moveToFirst())) {
-                int typeColumn = cursor.getColumnIndex(Contacts.ContactMethods.TYPE);
-                int labelColumn = cursor.getColumnIndex(Contacts.ContactMethods.LABEL);
-                int dataColumn = cursor.getColumnIndex(Contacts.ContactMethods.DATA);
-                int kindColumn = cursor.getColumnIndex(Contacts.ContactMethods.KIND);
-                do {
-                    int kind = cursor.getInt(kindColumn);
-
-                    switch(kind) {
-                    case Contacts.KIND_EMAIL:
-                        EmailData emailData = new EmailData(cursor.getInt(typeColumn),
-                                cursor.getString(labelColumn), cursor.getString(dataColumn));
-                        contact.mEmail.add(emailData);
-                        break;
-                    case Contacts.KIND_POSTAL:
-                        AddressData addr = new AddressData(cursor.getInt(typeColumn),
-                                cursor.getString(labelColumn), cursor.getString(dataColumn));
-                        contact.mAddr.add(addr);
-                        break;
-                    default:
-                        break;
-                    }
-                } while (cursor.moveToNext());
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private void readAllOrgs(ContactData contactData, int personId) {
-        final String[] projection = new String[] {
-                Contacts.Organizations.COMPANY,
-                Contacts.Organizations.TITLE,
-        };
-        String selection = String.format("%s=%d", Contacts.ContactMethods.PERSON_ID, personId);
-        Cursor cursor = null;
-        try {
-            cursor = mContentResolver.query(Contacts.Organizations.CONTENT_URI,
-                    projection, selection, null, null);
-            if ((cursor != null) && (cursor.moveToFirst())) {
-                int companyColumn = cursor.getColumnIndex(Contacts.Organizations.COMPANY);
-                int titleColumn = cursor.getColumnIndex(Contacts.Organizations.TITLE);
-                do {
-                    contactData.mOrg =  cursor.getString(companyColumn);
-                    contactData.mTitle = cursor.getString(titleColumn);
-                } while (cursor.moveToNext());
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private void readAllExtensions(ContactData contactData, int personId) {
-        final String[] projection = new String[] {
-                Contacts.Extensions.NAME,
-                Contacts.Extensions.VALUE,
-        };
-        String selection = String.format("%s=%d", Contacts.Extensions.PERSON_ID, personId);
-        Cursor cursor = null;
-        try {
-            cursor = mContentResolver.query(Contacts.Extensions.CONTENT_URI,
-                    projection, selection, null, null);
-            if ((cursor != null) && (cursor.moveToFirst())) {
-                int nameColumn = cursor.getColumnIndex(Contacts.Extensions.NAME);
-                int valueColumn = cursor.getColumnIndex(Contacts.Extensions.VALUE);
-                do {
-                    contactData.mExtensions.put(
-                            cursor.getString(nameColumn),
-                            cursor.getString(valueColumn));
-                } while (cursor.moveToNext());
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private String toHalfWidthString(String orgString) {
-        StringBuilder builder = new StringBuilder();
-        int length = orgString.length();
-        for (int i = 0; i < length; i++) {
-            // All Japanese character is able to be expressed by char.
-            // Do not need to use String#codepPointAt().
-            char ch = orgString.charAt(i);
-            CharSequence halfWidthText = JapaneseUtils.tryGetHalfWidthText(ch);
-            if (halfWidthText != null) {
-                builder.append(halfWidthText);
-            } else {
-                builder.append(ch);
-            }
-        }
-        return builder.toString();
-    }
-
-    private String encodeSomeCharacters(String str) {
-        char[] strArray = str.toCharArray();
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < strArray.length; i++) {
-            char ch = strArray[i];
-            switch (ch) {
-            case ';':
-                builder.append('\\');
-                builder.append(';');
-                break;
-            case '\r':
-            case '\n':
-                // ignore
-                break;
-            case '\\':
-            case '<':
-            case '>':
-                if (mVCardType == VCARD_TYPE_DOCOMO) {
-                    builder.append('\\');
-                    builder.append(ch);
-                }
-                break;
-            default:
-                builder.append(ch);
-                break;
-            }
-        }
-        return builder.toString();
-    }
-
-    private String convertContactToVCard(ContactData contactData) {
-        // Some DoCoMo mobile devices cannot parse a VCard data which does not have empty field.
-        final boolean isDoCoMo = (mVCardType == VCARD_TYPE_DOCOMO);
-        StringBuilder builder = new StringBuilder();
-        appendVCardLine(builder, VCARD_PROPERTY_BEGIN, VCARD_DATA_VCARD);
-        appendVCardLine(builder, VCARD_PROPERTY_VERSION, VCARD_DATA_VERSION_V21);
-
-        if (!TextUtils.isEmpty(contactData.mName)) {
-            builder.append(VCARD_PROPERTY_NAME);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(mVCardAttributeCharset);
-            builder.append(VCARD_DATA_SEPARATOR);
-            builder.append(encodeSomeCharacters(contactData.mName));
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
-        } else if (isDoCoMo) {
-            appendVCardLine(builder, VCARD_PROPERTY_NAME, "");
-        }
-
-        if (!TextUtils.isEmpty(contactData.mPhoneticName)) {
-            // Note: There is no appropriate property for expressing phonetic name in VCard 2.1,
-            //       while there is in VCard 3.0 (SORT-STRING).
-            //       We choose to use DoCoMo's way since it is supported by Japanese mobile phones.
-            builder.append(VCARD_PROPERTY_SOUND);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_X_IRMC_N);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(mVCardAttributeCharset);
-            builder.append(VCARD_DATA_SEPARATOR);
-            // TODO: Not only DoCoMo but also other Japanese mobile careers requires this.
-            String phoneticName =
-                (isDoCoMo ? toHalfWidthString(contactData.mPhoneticName) :
-                    contactData.mPhoneticName);
-            builder.append(encodeSomeCharacters(phoneticName));
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
-        } else if (isDoCoMo) {
-            // VCARD_ITEM_SEPARATOR should be inserted for DoCoMo devices.
-            builder.append(VCARD_PROPERTY_SOUND);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_X_IRMC_N);
-            builder.append(VCARD_DATA_SEPARATOR);
-            // Empty data.
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_COL_SEPARATOR);
-        }
-
-        if (contactData.mTel.size() > 0) {
-            for (TelData telData : contactData.mTel) {
-                appendVCardTelephoneLine(builder, telData.mType,
-                        telData.mLabel, telData.mValue);
-            }
-        } else if (isDoCoMo) {
-            appendVCardTelephoneLine(builder, Contacts.Phones.TYPE_HOME, "", "");
-        }
-
-        if (contactData.mEmail.size() > 0) {
-            for (EmailData emailData : contactData.mEmail) {
-                appendVCardEmailLine(builder, emailData.mType,
-                        emailData.mLabel, emailData.mValue);
-            }
-        } else if (isDoCoMo) {
-            appendVCardEmailLine(builder, Contacts.ContactMethods.TYPE_HOME, "", "");
-        }
-
-        if (isDoCoMo) {
-            appendVCardAddressLinesForDoCoMo(builder, contactData);
-        } else {
-            appendVCardAddressLinesForGeneric(builder, contactData);
-        }
-
-        if (!TextUtils.isEmpty(contactData.mOrg)) {
-            appendVCardLine(builder, VCARD_PROPERTY_ORG, contactData.mOrg, true, true);
-        }
-
-        if (!TextUtils.isEmpty(contactData.mTitle)) {
-            appendVCardLine(builder, VCARD_PROPERTY_TITLE, contactData.mTitle, true, true);
-        }
-
-        if (!TextUtils.isEmpty(contactData.mNote)) {
-            appendVCardLine(builder, VCARD_PROPERTY_NOTE, contactData.mNote, true, true);
-        }
-
-        if ((contactData.mPhoto != null) && (contactData.mPhoto.length() > 0)) {
-            // Note that contactData.mPhoto is already BASE64-encoded.
-            appendVCardPhotoLine(builder, contactData.mPhoto, contactData.mPhotoType);
-        }
-
-        appendVCardExtension(builder, contactData, VCARD_PROPERTY_BDAY, isDoCoMo);
-
-        // XXX: URL may have non-ascii chars. Should we add charset?
-        appendVCardExtension(builder, contactData, VCARD_PROPERTY_URL, isDoCoMo);
-
-        if (isDoCoMo) {
-            if (contactData.mExtensions.containsKey(VCARD_PROPERTY_X_CLASS)) {
-                appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_CLASS, true);
-            } else {
-                appendVCardLine(builder, VCARD_PROPERTY_X_CLASS, VCARD_DATA_PUBLIC);
-            }
-            appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_REDUCTION, true);
-            appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_NO, true);
-            appendVCardExtension(builder, contactData, VCARD_PROPERTY_X_DCM_HMN_MODE, true);
-        }
-
-        appendVCardLine(builder, VCARD_PROPERTY_END, VCARD_DATA_VCARD);
-
-        return builder.toString();
-    }
-
-    private void appendVCardAddressLinesForGeneric(StringBuilder builder, ContactData contactData) {
-        for (AddressData addr : contactData.mAddr) {
-            appendVCardAddressLine(builder, addr.mType, addr.mLabel, addr.mValue);
-        }
-    }
-
-    private void appendVCardAddressLinesForDoCoMo(StringBuilder builder, ContactData contactData) {
-        boolean isAddrSet = false;
-        for (AddressData addr : contactData.mAddr) {
-            if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_HOME)) {
-                appendVCardAddressLine(builder, addr.mType, addr.mLabel,
-                        addr.mValue);
-                isAddrSet = true;
-                break;
-            }
-        }
-        if (!isAddrSet) {
-            for (AddressData addr : contactData.mAddr) {
-                if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_WORK)) {
-                    appendVCardAddressLine(builder, addr.mType, addr.mLabel,
-                            addr.mValue);
-                    isAddrSet = true;
-                    break;
-                }
-            }
-        }
-        if (!isAddrSet) {
-            for (AddressData addr : contactData.mAddr) {
-                if ((!isAddrSet)  && (addr.mType == Contacts.ContactMethods.TYPE_OTHER)) {
-                    appendVCardAddressLine(builder, addr.mType, addr.mLabel,
-                            addr.mValue);
-                    isAddrSet = true;
-                    break;
-                }
-            }
-        }
-        if (!isAddrSet) {
-            for (AddressData addr : contactData.mAddr) {
-                if ((!isAddrSet) && (addr.mType == Contacts.ContactMethods.TYPE_CUSTOM)) {
-                    appendVCardAddressLine(builder, addr.mType, addr.mLabel,
-                            addr.mValue);
-                    isAddrSet = true;
-                    break;
-                }
-            }
-        }
-        if (!isAddrSet) {
-            appendVCardAddressLine(builder, Contacts.ContactMethods.TYPE_HOME, "", "");
-        }
-    }
-
-    private void appendVCardPhotoLine(StringBuilder builder, String encodedData, String type) {
-        StringBuilder tmpBuilder = new StringBuilder();
-        tmpBuilder.append(VCARD_PROPERTY_PHOTO);
-        tmpBuilder.append(VCARD_ATTR_SEPARATOR);
-        tmpBuilder.append(VCARD_ATTR_ENCODING_BASE64_V21);
-        tmpBuilder.append(VCARD_ATTR_SEPARATOR);
-        tmpBuilder.append("TYPE=");
-        tmpBuilder.append(type);
-        tmpBuilder.append(VCARD_DATA_SEPARATOR);
-        tmpBuilder.append(encodedData);
-
-        String tmpStr = tmpBuilder.toString();
-        tmpBuilder = new StringBuilder();
-        int lineCount = 0;
-        for (int i = 0; i < tmpStr.length(); i++) {
-            tmpBuilder.append(tmpStr.charAt(i));
-            lineCount++;
-            if (lineCount > 72) {
-                tmpBuilder.append(VCARD_COL_SEPARATOR);
-                tmpBuilder.append(VCARD_WS);
-                lineCount = 0;
-            }
-        }
-        builder.append(tmpBuilder.toString());
-        builder.append(VCARD_COL_SEPARATOR);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private void appendVCardAddressLine(StringBuilder builder,
-            int type, String label, String rawData) {
-        builder.append(VCARD_PROPERTY_ADR);
-        builder.append(VCARD_ATTR_SEPARATOR);
-
-        boolean dataExists = !TextUtils.isEmpty(rawData);
-
-        switch(type) {
-        case Contacts.ContactMethods.TYPE_HOME:
-            builder.append(VCARD_ATTR_HOME);
-            if (dataExists) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-            }
-            break;
-        case Contacts.ContactMethods.TYPE_WORK:
-            builder.append(VCARD_ATTR_WORK);
-            if (dataExists) {
-                builder.append(VCARD_ATTR_SEPARATOR);
-            }
-            break;
-        case Contacts.ContactMethods.TYPE_CUSTOM:
-            // Ignore custom value since
-            // - it may violate vCard spec
-            // - it may contain non-ASCII characters
-            // TODO: fix this.
-            //
-            // builder.append(label);
-            // builder.append(VCARD_DATA_SEPARATOR);
-            // break;
-        case Contacts.ContactMethods.TYPE_OTHER:
-        default:
-            // Ignore other methods.
-            // TODO: fix this.
-            break;
-        }
-
-        if (dataExists) {
-            builder.append(mVCardAttributeCharset);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_ENCODING_QP);
-        }
-        builder.append(VCARD_DATA_SEPARATOR);
-        if (dataExists) {
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(encodeQuotedPrintable(rawData));
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-            builder.append(VCARD_ITEM_SEPARATOR);
-        }
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private void appendVCardEmailLine(StringBuilder builder, int type, String label, String data) {
-        builder.append(VCARD_PROPERTY_EMAIL);
-        builder.append(VCARD_ATTR_SEPARATOR);
-
-        switch(type) {
-        case Contacts.ContactMethods.TYPE_CUSTOM:
-            if (label.equals(Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME)) {
-                builder.append(VCARD_ATTR_CELL);
-            } else {
-                // Ignore custom value.
-                builder.append(VCARD_ATTR_INTERNET);
-            }
-            break;
-        case Contacts.ContactMethods.TYPE_HOME:
-            builder.append(VCARD_ATTR_HOME);
-            break;
-        case Contacts.ContactMethods.TYPE_WORK:
-            builder.append(VCARD_ATTR_WORK);
-            break;
-        case Contacts.ContactMethods.TYPE_OTHER:
-        default:
-            builder.append(VCARD_ATTR_INTERNET);
-            break;
-        }
-
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(data);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private void appendVCardTelephoneLine(StringBuilder builder,
-            int type, String label, String data) {
-        builder.append(VCARD_PROPERTY_TEL);
-        builder.append(VCARD_ATTR_SEPARATOR);
-
-        switch(type) {
-        case Contacts.Phones.TYPE_CUSTOM:
-            // Ignore custom label.
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        case Contacts.Phones.TYPE_HOME:
-            builder.append(VCARD_ATTR_HOME);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        case Contacts.Phones.TYPE_MOBILE:
-            builder.append(VCARD_ATTR_CELL);
-            break;
-        case Contacts.Phones.TYPE_WORK:
-            builder.append(VCARD_ATTR_WORK);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        case Contacts.Phones.TYPE_FAX_WORK:
-            builder.append(VCARD_ATTR_WORK);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_FAX);
-            break;
-        case Contacts.Phones.TYPE_FAX_HOME:
-            builder.append(VCARD_ATTR_HOME);
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_FAX);
-            break;
-        case Contacts.Phones.TYPE_PAGER:
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        case Contacts.Phones.TYPE_OTHER:
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        default:
-            builder.append(VCARD_ATTR_VOICE);
-            break;
-        }
-
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(data);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    private void appendVCardExtension(StringBuilder builder, ContactData contactData,
-            String propertyName, boolean mustEmitSomething) {
-        if (contactData.mExtensions.containsKey(propertyName)) {
-            PropertyNode propertyNode =
-                PropertyNode.decode(contactData.mExtensions.get(propertyName));
-            appendVCardLine(builder, propertyName, propertyNode.propValue);
-        } else if (mustEmitSomething) {
-            appendVCardLine(builder, propertyName, "");
-        }
-    }
-
-    private void appendVCardLine(StringBuilder builder, String propertyName, String data) {
-        appendVCardLine(builder, propertyName, data, false, false);
-    }
-
-    private void appendVCardLine(StringBuilder builder, String field, String data,
-            boolean needCharset, boolean needQuotedPrintable) {
-        builder.append(field);
-        if (needCharset) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(mVCardAttributeCharset);
-        }
-
-        if (needQuotedPrintable) {
-            builder.append(VCARD_ATTR_SEPARATOR);
-            builder.append(VCARD_ATTR_ENCODING_QP);
-            data = encodeQuotedPrintable(data);
-        }
-
-        builder.append(VCARD_DATA_SEPARATOR);
-        builder.append(data);
-        builder.append(VCARD_COL_SEPARATOR);
-    }
-
-    // TODO: Replace with the method in Base64 class.
-    private static char PAD = '=';
-    private static final char[] ENCODE64 = {
-        'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
-        'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
-        'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
-        'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
-    };
-
-    private String encodeBase64(byte[] data) {
-        if (data == null) {
-            return "";
-        }
-
-        char[] charBuffer = new char[(data.length + 2) / 3 * 4];
-        int position = 0;
-        int _3byte = 0;
-        for (int i=0; i<data.length-2; i+=3) {
-            _3byte = ((data[i] & 0xFF) << 16) + ((data[i+1] & 0xFF) << 8) + (data[i+2] & 0xFF);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
-            charBuffer[position++] = ENCODE64[_3byte & 0x3F];
-        }
-        switch(data.length % 3) {
-        case 1: // [111111][11 0000][0000 00][000000]
-            _3byte = ((data[data.length-1] & 0xFF) << 16);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = PAD;
-            charBuffer[position++] = PAD;
-            break;
-        case 2: // [111111][11 1111][1111 00][000000]
-            _3byte = ((data[data.length-2] & 0xFF) << 16) + ((data[data.length-1] & 0xFF) << 8);
-            charBuffer[position++] = ENCODE64[_3byte >> 18];
-            charBuffer[position++] = ENCODE64[(_3byte >> 12) & 0x3F];
-            charBuffer[position++] = ENCODE64[(_3byte >>  6) & 0x3F];
-            charBuffer[position++] = PAD;
-            break;
-        }
-
-        return new String(charBuffer);
-    }
-
-    private String encodeQuotedPrintable(String str) {
-        {
-            // Replace "\n" and "\r" with "\r\n".
-            StringBuilder tmpBuilder = new StringBuilder();
-            int length = str.length();
-            for (int i = 0; i < length; i++) {
-                char ch = str.charAt(i);
-                if (ch == '\r') {
-                    if (i + 1 < length && str.charAt(i + 1) == '\n') {
-                        i++;
-                    }
-                    tmpBuilder.append("\r\n");
-                } else if (ch == '\n') {
-                    tmpBuilder.append("\r\n");
-                } else {
-                    tmpBuilder.append(str.charAt(i));
-                }
-            }
-            str = tmpBuilder.toString();
-        }
-
-        StringBuilder builder = new StringBuilder();
-        int index = 0;
-        int lineCount = 0;
-        byte[] strArray = null;
-
-        try {
-            strArray = str.getBytes(mCharsetString);
-        } catch (UnsupportedEncodingException e) {
-            Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. " +
-                    "Try default charset");
-            strArray = str.getBytes();
-        }
-        while (index < strArray.length) {
-            builder.append(String.format("=%02X", strArray[index]));
-            index += 1;
-            lineCount += 3;
-
-            if (lineCount >= 67) {
-                // Specification requires CRLF must be inserted before the length of the line
-                // becomes more than 76.
-                // Assuming that the next character is a multi-byte character, it will become
-                // 6 bytes.
-                // 76 - 6 - 3 = 67
-                builder.append("=\r\n");
-                lineCount = 0;
-            }
-        }
-
-        return builder.toString();
-    }
-
-    // TODO: replace this with ContactStruct
-    public class ContactData {
-        private String mName = "";
-        private String mPhoneticName = "";
-        private ArrayList<TelData> mTel = new ArrayList<TelData>();
-        private ArrayList<EmailData> mEmail = new ArrayList<EmailData>();
-        private ArrayList<AddressData> mAddr = new ArrayList<AddressData>();
-        private String mOrg = "";
-        private String mTitle = "";
-        private String mNote = "";
-        private String mPhoto = "";
-        private String mPhotoType = "JPG"; // Default
-        private Map<String, String> mExtensions = new HashMap<String, String>();
-
-        public boolean isEmptyName() {
-            return TextUtils.isEmpty(mName);
-        }
-    }
-
-    private class AddressData {
-        private int mType;
-        private String mLabel;
-        private String mValue;
-
-        public AddressData(int type, String label, String value) {
-            mType = type;
-            mLabel = label;
-            mValue = value;
-        }
-    }
-
-    private class EmailData {
-        private int mType;
-        private String mLabel;
-        private String mValue;
-
-        public EmailData(int type, String label, String value) {
-            mType = type;
-            mLabel = label;
-            mValue = value;
-        }
-    }
-
-    private class TelData {
-        private int mType;
-        private String mLabel;
-        private String mValue;
-
-        public TelData(int type, String label, String value) {
-            mType = type;
-            mLabel = label;
-            mValue = value;
-        }
-    }
-}
-
-/**
- * TextUtils especially for Japanese.
- * TODO: make this in android.text in the future
- */
-class JapaneseUtils {
-    static private final Map<Character, String> sHalfWidthMap =
-        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");
-        sHalfWidthMap.put('\u300D', "\uFF63");
-        sHalfWidthMap.put('\u301C', "~");
-        sHalfWidthMap.put('\u3041', "\uFF67");
-        sHalfWidthMap.put('\u3042', "\uFF71");
-        sHalfWidthMap.put('\u3043', "\uFF68");
-        sHalfWidthMap.put('\u3044', "\uFF72");
-        sHalfWidthMap.put('\u3045', "\uFF69");
-        sHalfWidthMap.put('\u3046', "\uFF73");
-        sHalfWidthMap.put('\u3047', "\uFF6A");
-        sHalfWidthMap.put('\u3048', "\uFF74");
-        sHalfWidthMap.put('\u3049', "\uFF6B");
-        sHalfWidthMap.put('\u304A', "\uFF75");
-        sHalfWidthMap.put('\u304B', "\uFF76");
-        sHalfWidthMap.put('\u304C', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u304D', "\uFF77");
-        sHalfWidthMap.put('\u304E', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u304F', "\uFF78");
-        sHalfWidthMap.put('\u3050', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u3051', "\uFF79");
-        sHalfWidthMap.put('\u3052', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u3053', "\uFF7A");
-        sHalfWidthMap.put('\u3054', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u3055', "\uFF7B");
-        sHalfWidthMap.put('\u3056', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u3057', "\uFF7C");
-        sHalfWidthMap.put('\u3058', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u3059', "\uFF7D");
-        sHalfWidthMap.put('\u305A', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u305B', "\uFF7E");
-        sHalfWidthMap.put('\u305C', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u305D', "\uFF7F");
-        sHalfWidthMap.put('\u305E', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u305F', "\uFF80");
-        sHalfWidthMap.put('\u3060', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u3061', "\uFF81");
-        sHalfWidthMap.put('\u3062', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u3063', "\uFF6F");
-        sHalfWidthMap.put('\u3064', "\uFF82");
-        sHalfWidthMap.put('\u3065', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u3066', "\uFF83");
-        sHalfWidthMap.put('\u3067', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u3068', "\uFF84");
-        sHalfWidthMap.put('\u3069', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u306A', "\uFF85");
-        sHalfWidthMap.put('\u306B', "\uFF86");
-        sHalfWidthMap.put('\u306C', "\uFF87");
-        sHalfWidthMap.put('\u306D', "\uFF88");
-        sHalfWidthMap.put('\u306E', "\uFF89");
-        sHalfWidthMap.put('\u306F', "\uFF8A");
-        sHalfWidthMap.put('\u3070', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u3071', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u3072', "\uFF8B");
-        sHalfWidthMap.put('\u3073', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u3074', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u3075', "\uFF8C");
-        sHalfWidthMap.put('\u3076', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u3077', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u3078', "\uFF8D");
-        sHalfWidthMap.put('\u3079', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u307A', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u307B', "\uFF8E");
-        sHalfWidthMap.put('\u307C', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u307D', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u307E', "\uFF8F");
-        sHalfWidthMap.put('\u307F', "\uFF90");
-        sHalfWidthMap.put('\u3080', "\uFF91");
-        sHalfWidthMap.put('\u3081', "\uFF92");
-        sHalfWidthMap.put('\u3082', "\uFF93");
-        sHalfWidthMap.put('\u3083', "\uFF6C");
-        sHalfWidthMap.put('\u3084', "\uFF94");
-        sHalfWidthMap.put('\u3085', "\uFF6D");
-        sHalfWidthMap.put('\u3086', "\uFF95");
-        sHalfWidthMap.put('\u3087', "\uFF6E");
-        sHalfWidthMap.put('\u3088', "\uFF96");
-        sHalfWidthMap.put('\u3089', "\uFF97");
-        sHalfWidthMap.put('\u308A', "\uFF98");
-        sHalfWidthMap.put('\u308B', "\uFF99");
-        sHalfWidthMap.put('\u308C', "\uFF9A");
-        sHalfWidthMap.put('\u308D', "\uFF9B");
-        sHalfWidthMap.put('\u308E', "\uFF9C");
-        sHalfWidthMap.put('\u308F', "\uFF9C");
-        sHalfWidthMap.put('\u3090', "\uFF72");
-        sHalfWidthMap.put('\u3091', "\uFF74");
-        sHalfWidthMap.put('\u3092', "\uFF66");
-        sHalfWidthMap.put('\u3093', "\uFF9D");
-        sHalfWidthMap.put('\u309B', "\uFF9E");
-        sHalfWidthMap.put('\u309C', "\uFF9F");
-        sHalfWidthMap.put('\u30A1', "\uFF67");
-        sHalfWidthMap.put('\u30A2', "\uFF71");
-        sHalfWidthMap.put('\u30A3', "\uFF68");
-        sHalfWidthMap.put('\u30A4', "\uFF72");
-        sHalfWidthMap.put('\u30A5', "\uFF69");
-        sHalfWidthMap.put('\u30A6', "\uFF73");
-        sHalfWidthMap.put('\u30A7', "\uFF6A");
-        sHalfWidthMap.put('\u30A8', "\uFF74");
-        sHalfWidthMap.put('\u30A9', "\uFF6B");
-        sHalfWidthMap.put('\u30AA', "\uFF75");
-        sHalfWidthMap.put('\u30AB', "\uFF76");
-        sHalfWidthMap.put('\u30AC', "\uFF76\uFF9E");
-        sHalfWidthMap.put('\u30AD', "\uFF77");
-        sHalfWidthMap.put('\u30AE', "\uFF77\uFF9E");
-        sHalfWidthMap.put('\u30AF', "\uFF78");
-        sHalfWidthMap.put('\u30B0', "\uFF78\uFF9E");
-        sHalfWidthMap.put('\u30B1', "\uFF79");
-        sHalfWidthMap.put('\u30B2', "\uFF79\uFF9E");
-        sHalfWidthMap.put('\u30B3', "\uFF7A");
-        sHalfWidthMap.put('\u30B4', "\uFF7A\uFF9E");
-        sHalfWidthMap.put('\u30B5', "\uFF7B");
-        sHalfWidthMap.put('\u30B6', "\uFF7B\uFF9E");
-        sHalfWidthMap.put('\u30B7', "\uFF7C");
-        sHalfWidthMap.put('\u30B8', "\uFF7C\uFF9E");
-        sHalfWidthMap.put('\u30B9', "\uFF7D");
-        sHalfWidthMap.put('\u30BA', "\uFF7D\uFF9E");
-        sHalfWidthMap.put('\u30BB', "\uFF7E");
-        sHalfWidthMap.put('\u30BC', "\uFF7E\uFF9E");
-        sHalfWidthMap.put('\u30BD', "\uFF7F");
-        sHalfWidthMap.put('\u30BE', "\uFF7F\uFF9E");
-        sHalfWidthMap.put('\u30BF', "\uFF80");
-        sHalfWidthMap.put('\u30C0', "\uFF80\uFF9E");
-        sHalfWidthMap.put('\u30C1', "\uFF81");
-        sHalfWidthMap.put('\u30C2', "\uFF81\uFF9E");
-        sHalfWidthMap.put('\u30C3', "\uFF6F");
-        sHalfWidthMap.put('\u30C4', "\uFF82");
-        sHalfWidthMap.put('\u30C5', "\uFF82\uFF9E");
-        sHalfWidthMap.put('\u30C6', "\uFF83");
-        sHalfWidthMap.put('\u30C7', "\uFF83\uFF9E");
-        sHalfWidthMap.put('\u30C8', "\uFF84");
-        sHalfWidthMap.put('\u30C9', "\uFF84\uFF9E");
-        sHalfWidthMap.put('\u30CA', "\uFF85");
-        sHalfWidthMap.put('\u30CB', "\uFF86");
-        sHalfWidthMap.put('\u30CC', "\uFF87");
-        sHalfWidthMap.put('\u30CD', "\uFF88");
-        sHalfWidthMap.put('\u30CE', "\uFF89");
-        sHalfWidthMap.put('\u30CF', "\uFF8A");
-        sHalfWidthMap.put('\u30D0', "\uFF8A\uFF9E");
-        sHalfWidthMap.put('\u30D1', "\uFF8A\uFF9F");
-        sHalfWidthMap.put('\u30D2', "\uFF8B");
-        sHalfWidthMap.put('\u30D3', "\uFF8B\uFF9E");
-        sHalfWidthMap.put('\u30D4', "\uFF8B\uFF9F");
-        sHalfWidthMap.put('\u30D5', "\uFF8C");
-        sHalfWidthMap.put('\u30D6', "\uFF8C\uFF9E");
-        sHalfWidthMap.put('\u30D7', "\uFF8C\uFF9F");
-        sHalfWidthMap.put('\u30D8', "\uFF8D");
-        sHalfWidthMap.put('\u30D9', "\uFF8D\uFF9E");
-        sHalfWidthMap.put('\u30DA', "\uFF8D\uFF9F");
-        sHalfWidthMap.put('\u30DB', "\uFF8E");
-        sHalfWidthMap.put('\u30DC', "\uFF8E\uFF9E");
-        sHalfWidthMap.put('\u30DD', "\uFF8E\uFF9F");
-        sHalfWidthMap.put('\u30DE', "\uFF8F");
-        sHalfWidthMap.put('\u30DF', "\uFF90");
-        sHalfWidthMap.put('\u30E0', "\uFF91");
-        sHalfWidthMap.put('\u30E1', "\uFF92");
-        sHalfWidthMap.put('\u30E2', "\uFF93");
-        sHalfWidthMap.put('\u30E3', "\uFF6C");
-        sHalfWidthMap.put('\u30E4', "\uFF94");
-        sHalfWidthMap.put('\u30E5', "\uFF6D");
-        sHalfWidthMap.put('\u30E6', "\uFF95");
-        sHalfWidthMap.put('\u30E7', "\uFF6E");
-        sHalfWidthMap.put('\u30E8', "\uFF96");
-        sHalfWidthMap.put('\u30E9', "\uFF97");
-        sHalfWidthMap.put('\u30EA', "\uFF98");
-        sHalfWidthMap.put('\u30EB', "\uFF99");
-        sHalfWidthMap.put('\u30EC', "\uFF9A");
-        sHalfWidthMap.put('\u30ED', "\uFF9B");
-        sHalfWidthMap.put('\u30EE', "\uFF9C");
-        sHalfWidthMap.put('\u30EF', "\uFF9C");
-        sHalfWidthMap.put('\u30F0', "\uFF72");
-        sHalfWidthMap.put('\u30F1', "\uFF74");
-        sHalfWidthMap.put('\u30F2', "\uFF66");
-        sHalfWidthMap.put('\u30F3', "\uFF9D");
-        sHalfWidthMap.put('\u30F4', "\uFF73\uFF9E");
-        sHalfWidthMap.put('\u30F5', "\uFF76");
-        sHalfWidthMap.put('\u30F6', "\uFF79");
-        sHalfWidthMap.put('\u30FB', "\uFF65");
-        sHalfWidthMap.put('\u30FC', "\uFF70");
-        sHalfWidthMap.put('\uFF01', "!");
-        sHalfWidthMap.put('\uFF02', "\"");
-        sHalfWidthMap.put('\uFF03', "#");
-        sHalfWidthMap.put('\uFF04', "$");
-        sHalfWidthMap.put('\uFF05', "%");
-        sHalfWidthMap.put('\uFF06', "&");
-        sHalfWidthMap.put('\uFF07', "'");
-        sHalfWidthMap.put('\uFF08', "(");
-        sHalfWidthMap.put('\uFF09', ")");
-        sHalfWidthMap.put('\uFF0A', "*");
-        sHalfWidthMap.put('\uFF0B', "+");
-        sHalfWidthMap.put('\uFF0C', ",");
-        sHalfWidthMap.put('\uFF0D', "-");
-        sHalfWidthMap.put('\uFF0E', ".");
-        sHalfWidthMap.put('\uFF0F', "/");
-        sHalfWidthMap.put('\uFF10', "0");
-        sHalfWidthMap.put('\uFF11', "1");
-        sHalfWidthMap.put('\uFF12', "2");
-        sHalfWidthMap.put('\uFF13', "3");
-        sHalfWidthMap.put('\uFF14', "4");
-        sHalfWidthMap.put('\uFF15', "5");
-        sHalfWidthMap.put('\uFF16', "6");
-        sHalfWidthMap.put('\uFF17', "7");
-        sHalfWidthMap.put('\uFF18', "8");
-        sHalfWidthMap.put('\uFF19', "9");
-        sHalfWidthMap.put('\uFF1A', ":");
-        sHalfWidthMap.put('\uFF1B', ";");
-        sHalfWidthMap.put('\uFF1C', "<");
-        sHalfWidthMap.put('\uFF1D', "=");
-        sHalfWidthMap.put('\uFF1E', ">");
-        sHalfWidthMap.put('\uFF1F', "?");
-        sHalfWidthMap.put('\uFF20', "@");
-        sHalfWidthMap.put('\uFF21', "A");
-        sHalfWidthMap.put('\uFF22', "B");
-        sHalfWidthMap.put('\uFF23', "C");
-        sHalfWidthMap.put('\uFF24', "D");
-        sHalfWidthMap.put('\uFF25', "E");
-        sHalfWidthMap.put('\uFF26', "F");
-        sHalfWidthMap.put('\uFF27', "G");
-        sHalfWidthMap.put('\uFF28', "H");
-        sHalfWidthMap.put('\uFF29', "I");
-        sHalfWidthMap.put('\uFF2A', "J");
-        sHalfWidthMap.put('\uFF2B', "K");
-        sHalfWidthMap.put('\uFF2C', "L");
-        sHalfWidthMap.put('\uFF2D', "M");
-        sHalfWidthMap.put('\uFF2E', "N");
-        sHalfWidthMap.put('\uFF2F', "O");
-        sHalfWidthMap.put('\uFF30', "P");
-        sHalfWidthMap.put('\uFF31', "Q");
-        sHalfWidthMap.put('\uFF32', "R");
-        sHalfWidthMap.put('\uFF33', "S");
-        sHalfWidthMap.put('\uFF34', "T");
-        sHalfWidthMap.put('\uFF35', "U");
-        sHalfWidthMap.put('\uFF36', "V");
-        sHalfWidthMap.put('\uFF37', "W");
-        sHalfWidthMap.put('\uFF38', "X");
-        sHalfWidthMap.put('\uFF39', "Y");
-        sHalfWidthMap.put('\uFF3A', "Z");
-        sHalfWidthMap.put('\uFF3B', "[");
-        sHalfWidthMap.put('\uFF3C', "\\");
-        sHalfWidthMap.put('\uFF3D', "]");
-        sHalfWidthMap.put('\uFF3E', "^");
-        sHalfWidthMap.put('\uFF3F', "_");
-        sHalfWidthMap.put('\uFF41', "a");
-        sHalfWidthMap.put('\uFF42', "b");
-        sHalfWidthMap.put('\uFF43', "c");
-        sHalfWidthMap.put('\uFF44', "d");
-        sHalfWidthMap.put('\uFF45', "e");
-        sHalfWidthMap.put('\uFF46', "f");
-        sHalfWidthMap.put('\uFF47', "g");
-        sHalfWidthMap.put('\uFF48', "h");
-        sHalfWidthMap.put('\uFF49', "i");
-        sHalfWidthMap.put('\uFF4A', "j");
-        sHalfWidthMap.put('\uFF4B', "k");
-        sHalfWidthMap.put('\uFF4C', "l");
-        sHalfWidthMap.put('\uFF4D', "m");
-        sHalfWidthMap.put('\uFF4E', "n");
-        sHalfWidthMap.put('\uFF4F', "o");
-        sHalfWidthMap.put('\uFF50', "p");
-        sHalfWidthMap.put('\uFF51', "q");
-        sHalfWidthMap.put('\uFF52', "r");
-        sHalfWidthMap.put('\uFF53', "s");
-        sHalfWidthMap.put('\uFF54', "t");
-        sHalfWidthMap.put('\uFF55', "u");
-        sHalfWidthMap.put('\uFF56', "v");
-        sHalfWidthMap.put('\uFF57', "w");
-        sHalfWidthMap.put('\uFF58', "x");
-        sHalfWidthMap.put('\uFF59', "y");
-        sHalfWidthMap.put('\uFF5A', "z");
-        sHalfWidthMap.put('\uFF5B', "{");
-        sHalfWidthMap.put('\uFF5C', "|");
-        sHalfWidthMap.put('\uFF5D', "}");
-        sHalfWidthMap.put('\uFF5E', "~");
-        sHalfWidthMap.put('\uFF61', "\uFF61");
-        sHalfWidthMap.put('\uFF62', "\uFF62");
-        sHalfWidthMap.put('\uFF63', "\uFF63");
-        sHalfWidthMap.put('\uFF64', "\uFF64");
-        sHalfWidthMap.put('\uFF65', "\uFF65");
-        sHalfWidthMap.put('\uFF66', "\uFF66");
-        sHalfWidthMap.put('\uFF67', "\uFF67");
-        sHalfWidthMap.put('\uFF68', "\uFF68");
-        sHalfWidthMap.put('\uFF69', "\uFF69");
-        sHalfWidthMap.put('\uFF6A', "\uFF6A");
-        sHalfWidthMap.put('\uFF6B', "\uFF6B");
-        sHalfWidthMap.put('\uFF6C', "\uFF6C");
-        sHalfWidthMap.put('\uFF6D', "\uFF6D");
-        sHalfWidthMap.put('\uFF6E', "\uFF6E");
-        sHalfWidthMap.put('\uFF6F', "\uFF6F");
-        sHalfWidthMap.put('\uFF70', "\uFF70");
-        sHalfWidthMap.put('\uFF71', "\uFF71");
-        sHalfWidthMap.put('\uFF72', "\uFF72");
-        sHalfWidthMap.put('\uFF73', "\uFF73");
-        sHalfWidthMap.put('\uFF74', "\uFF74");
-        sHalfWidthMap.put('\uFF75', "\uFF75");
-        sHalfWidthMap.put('\uFF76', "\uFF76");
-        sHalfWidthMap.put('\uFF77', "\uFF77");
-        sHalfWidthMap.put('\uFF78', "\uFF78");
-        sHalfWidthMap.put('\uFF79', "\uFF79");
-        sHalfWidthMap.put('\uFF7A', "\uFF7A");
-        sHalfWidthMap.put('\uFF7B', "\uFF7B");
-        sHalfWidthMap.put('\uFF7C', "\uFF7C");
-        sHalfWidthMap.put('\uFF7D', "\uFF7D");
-        sHalfWidthMap.put('\uFF7E', "\uFF7E");
-        sHalfWidthMap.put('\uFF7F', "\uFF7F");
-        sHalfWidthMap.put('\uFF80', "\uFF80");
-        sHalfWidthMap.put('\uFF81', "\uFF81");
-        sHalfWidthMap.put('\uFF82', "\uFF82");
-        sHalfWidthMap.put('\uFF83', "\uFF83");
-        sHalfWidthMap.put('\uFF84', "\uFF84");
-        sHalfWidthMap.put('\uFF85', "\uFF85");
-        sHalfWidthMap.put('\uFF86', "\uFF86");
-        sHalfWidthMap.put('\uFF87', "\uFF87");
-        sHalfWidthMap.put('\uFF88', "\uFF88");
-        sHalfWidthMap.put('\uFF89', "\uFF89");
-        sHalfWidthMap.put('\uFF8A', "\uFF8A");
-        sHalfWidthMap.put('\uFF8B', "\uFF8B");
-        sHalfWidthMap.put('\uFF8C', "\uFF8C");
-        sHalfWidthMap.put('\uFF8D', "\uFF8D");
-        sHalfWidthMap.put('\uFF8E', "\uFF8E");
-        sHalfWidthMap.put('\uFF8F', "\uFF8F");
-        sHalfWidthMap.put('\uFF90', "\uFF90");
-        sHalfWidthMap.put('\uFF91', "\uFF91");
-        sHalfWidthMap.put('\uFF92', "\uFF92");
-        sHalfWidthMap.put('\uFF93', "\uFF93");
-        sHalfWidthMap.put('\uFF94', "\uFF94");
-        sHalfWidthMap.put('\uFF95', "\uFF95");
-        sHalfWidthMap.put('\uFF96', "\uFF96");
-        sHalfWidthMap.put('\uFF97', "\uFF97");
-        sHalfWidthMap.put('\uFF98', "\uFF98");
-        sHalfWidthMap.put('\uFF99', "\uFF99");
-        sHalfWidthMap.put('\uFF9A', "\uFF9A");
-        sHalfWidthMap.put('\uFF9B', "\uFF9B");
-        sHalfWidthMap.put('\uFF9C', "\uFF9C");
-        sHalfWidthMap.put('\uFF9D', "\uFF9D");
-        sHalfWidthMap.put('\uFF9E', "\uFF9E");
-        sHalfWidthMap.put('\uFF9F', "\uFF9F");
-        sHalfWidthMap.put('\uFFE5', "\u005C\u005C");
-    }
-
-    /**
-     * Return half-width version of that character if possible. Return null if not possible
-     * @param ch input character
-     * @return CharSequence object if the mapping for ch exists. Return null otherwise.
-     */
-    public static CharSequence tryGetHalfWidthText(char ch) {
-        if (sHalfWidthMap.containsKey(ch)) {
-            return sHalfWidthMap.get(ch);
-        } else {
-            return null;
-        }
-    }
-}
+}
\ No newline at end of file