Make ImportVCardActivity cache vCard file into local data directory.

Bug: 2612502
Change-Id: I169bc481ec42ac97d48c91efbff6ad53df986008
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 3da8741..f84da9c 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -47,7 +47,7 @@
     <item type="id" name="dialog_select_import_type"/>
     <item type="id" name="dialog_select_one_vcard"/>
     <item type="id" name="dialog_select_multiple_vcard"/>
-    <item type="id" name="dialog_reading_vcard"/>
+    <item type="id" name="dialog_cache_vcard"/>
     <item type="id" name="dialog_io_exception"/>
     <item type="id" name="dialog_error_with_message"/>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e95dc3c..452b72a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -717,6 +717,9 @@
          (with extension ".vcf" in SDCard.) -->
     <string name="fail_reason_no_vcard_file">No vCard file found on the SD card</string>
 
+    <!-- Fail reason shown when vCard importer failed to look over meta information stored in vCard file(s). -->
+    <string name="fail_reason_failed_to_collect_vcard_meta_info">Failed to collect meta information of given vCard file(s).</string>
+
     <!-- The failed reason shown when the import of some of vCard files failed during multiple vCard
          files import. It includes the case where all files were failed to be imported. -->
     <string name="fail_reason_failed_to_read_files">One or more files failed to be imported (%s).</string>
@@ -727,6 +730,14 @@
     <!-- Dialog title shown when a user is asked to select vCard file -->
     <string name="select_vcard_title">Select vCard file</string>
 
+    <!-- The title shown when vCard importer is caching files to be imported into local temporary
+         data storage. -->
+    <string name="caching_vcard_title">Caching vCard(s) to local temporary storage</string>
+
+    <!-- The message shown when vCard importer is caching files to be imported into local temporary
+         data storage. -->
+    <string name="caching_vcard_message">Importer is caching vCard(s) to local temporary storage. Actual import will start soon.</string>
+
     <!-- The message shown while reading vCard(s).
          First argument is current index of contacts to be imported.
          Second argument is the total number of contacts.
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 7ad9228..164f539 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -21,6 +21,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.ProgressDialog;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -39,12 +40,29 @@
 
 import com.android.contacts.model.Sources;
 import com.android.contacts.util.AccountSelectionUtil;
+import com.android.vcard.VCardEntryCounter;
+import com.android.vcard.VCardInterpreterCollection;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.VCardSourceDetector;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardNestedException;
+import com.android.vcard.exception.VCardNotSupportedException;
+import com.android.vcard.exception.VCardVersionException;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.Channel;
+import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
 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;
@@ -67,7 +85,11 @@
 
     private static final int SELECT_ACCOUNT = 0;
 
-    /* package */ static final String VCARD_URI_ARRAY = "vcard_uri_array";
+    /* package */ static final String VCARD_URI_ARRAY = "vcard_uri";
+    /* package */ static final String ESTIMATED_VCARD_TYPE_ARRAY = "estimated_vcard_type";
+    /* package */ static final String ESTIMATED_CHARSET_ARRAY = "estimated_charset";
+    /* package */ static final String USE_V30_ARRAY = "use_v30";
+    /* package */ static final String ENTRY_COUNT_ARRAY = "entry_count";
 
     // Run on the UI thread. Must not be null except after onDestroy().
     private Handler mHandler = new Handler();
@@ -80,10 +102,13 @@
     private Uri mUri;
 
     private ProgressDialog mProgressDialogForScanVCard;
+    private ProgressDialog mProgressDialogForCacheVCard;
 
     private List<VCardFile> mAllVCardFileList;
     private VCardScanThread mVCardScanThread;
 
+    private VCardCacheThread mVCardCacheThread;
+
     private String mErrorMessage;
 
     private static class VCardFile {
@@ -138,6 +163,288 @@
 
     private CancelListener mCancelListener = new CancelListener();
 
+    /**
+     * Caches all vCard data into local data directory so that we allow
+     * {@link ImportVCardService} to access all the contents in given Uris, some of
+     * which may not be accessible from other components due to permission problem.
+     * (Activity which gives the Uri may allow only this Activity to access that content,
+     * not the ohter components like {@link ImportVCardService}.
+     *
+     * We also allow the Service to happen to exit during the vCard import procedure.
+     */
+    private class VCardCacheThread extends Thread
+            implements DialogInterface.OnCancelListener {
+        private static final String CACHE_FILE_PREFIX = "import_tmp_";
+        private boolean mCanceled;
+        private PowerManager.WakeLock mWakeLock;
+        private VCardParser mVCardParser;
+        private final Uri[] mSourceUris;
+
+        // Not using Uri[] since we throw this object to Service via Intent.
+        private final String[] mDestUriStrings;
+
+        private final int[] mEstimatedVCardTypes;
+        private final String[] mEstimatedCharsets;
+        private final boolean[] mShouldUseV30;
+        private final int[] mEntryCounts;
+
+        public VCardCacheThread(final Uri[] sourceUris) {
+            mSourceUris = sourceUris;
+            final int length = sourceUris.length;
+
+            mDestUriStrings = new String[length];
+            mEstimatedVCardTypes = new int[length];
+            mEstimatedCharsets = new String[length];
+            mShouldUseV30 = new boolean[length];
+            mEntryCounts = new int[length];
+
+            final Context context = ImportVCardActivity.this;
+            PowerManager powerManager = (PowerManager)context.getSystemService(
+                    Context.POWER_SERVICE);
+            mWakeLock = powerManager.newWakeLock(
+                    PowerManager.SCREEN_DIM_WAKE_LOCK |
+                    PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+        }
+
+        @Override
+        public void finalize() {
+            if (mWakeLock != null && mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+
+        @Override
+        public void run() {
+            final Context context = ImportVCardActivity.this;
+            final ContentResolver resolver = context.getContentResolver();
+            String errorMessage = null;
+            mWakeLock.acquire();
+
+            try {
+                clearOldCache();
+
+                // We may be able to read content of each vCard file during copying them
+                // to local storage, but currently vCard code does not allow us to do so.
+                //
+                // Note that Uris given from caller applications may not be opened twice,
+                // so we have to use local data after this copy instead of relying on
+                // the original source Uris.
+                copyVCardToLocal();
+                if (mCanceled) {
+                    return;
+                }
+                Log.d("@@@", "Caching done. Count the number of vCard entries.");
+                if (!collectVCardMetaInfo()) {
+                    Log.e(LOG_TAG, "Failed to collect vCard meta information");
+                    runOnUIThread(new DialogDisplayer(
+                            getString(R.string.fail_reason_failed_to_collect_vcard_meta_info)));
+                    return;
+                }
+                if (mCanceled) {
+                    return;
+                }
+                for (int i = 0; i < mEntryCounts.length; i++) {
+                    Log.d("@@@", String.format("length for %s: %d",
+                            mDestUriStrings[i], mEntryCounts[i]));
+                }
+            } catch (OutOfMemoryError e) {
+                Log.e(LOG_TAG, "OutOfMemoryError");
+                // We should take care of this case since Android devices may have
+                // smaller memory than we usually expect.
+                System.gc();
+                runOnUIThread(new DialogDisplayer(
+                        getString(R.string.fail_reason_io_error) +
+                        ": " + e.getLocalizedMessage()));
+                return;
+            } catch (IOException e) {
+                Log.e(LOG_TAG, e.getMessage());
+                runOnUIThread(new DialogDisplayer(
+                        getString(R.string.fail_reason_io_error) +
+                        ": " + e.getLocalizedMessage()));
+                return;
+            } finally {
+
+                mWakeLock.release();
+                mProgressDialogForCacheVCard.dismiss();
+            }
+
+            // TODO(dmiyakawa): do we need runOnUIThread?
+            final Intent intent = new Intent(
+                    ImportVCardActivity.this, ImportVCardService.class);
+            intent.putExtra(VCARD_URI_ARRAY, mDestUriStrings);
+            intent.putExtra(SelectAccountActivity.ACCOUNT_NAME, mAccountName);
+            intent.putExtra(SelectAccountActivity.ACCOUNT_TYPE, mAccountType);
+            intent.putExtra(ESTIMATED_VCARD_TYPE_ARRAY, mEstimatedVCardTypes);
+            intent.putExtra(ESTIMATED_CHARSET_ARRAY, mEstimatedCharsets);
+            intent.putExtra(USE_V30_ARRAY, mShouldUseV30);
+            intent.putExtra(ENTRY_COUNT_ARRAY, mEntryCounts);
+            startService(intent);
+            finish();
+        }
+
+
+        private boolean collectVCardMetaInfo() {
+            final ContentResolver resolver =
+                    ImportVCardActivity.this.getContentResolver();
+            final int length = mDestUriStrings.length;
+            try {
+                for (int i = 0; i < length; i++) {                     
+                    if (mCanceled) {
+                        return false;
+                    }
+                    final Uri uri = Uri.parse(mDestUriStrings[i]);
+                    boolean shouldUseV30 = false;
+                    InputStream is;
+                    VCardEntryCounter counter;
+                    VCardSourceDetector detector;
+                    VCardInterpreterCollection interpreter;
+
+                    is = resolver.openInputStream(uri);
+                    mVCardParser = new VCardParser_V21();
+                    try {
+                        counter = new VCardEntryCounter();
+                        detector = new VCardSourceDetector();
+                        interpreter =
+                                new VCardInterpreterCollection(
+                                        Arrays.asList(counter, detector));
+                        mVCardParser.parse(is, interpreter);
+                    } catch (VCardVersionException e1) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                        }
+
+                        shouldUseV30 = true;
+                        is = resolver.openInputStream(uri);
+                        mVCardParser = new VCardParser_V30();
+                        try {
+                            counter = new VCardEntryCounter();
+                            detector = new VCardSourceDetector();
+                            interpreter =
+                                    new VCardInterpreterCollection(
+                                            Arrays.asList(counter, detector));
+                            mVCardParser.parse(is, interpreter);
+                        } catch (VCardVersionException e2) {
+                            throw new VCardException("vCard with unspported version.");
+                        }
+                    } finally {
+                        if (is != null) {
+                            try {
+                                is.close();
+                            } catch (IOException e) {
+                            }
+                        }
+                    }
+
+                    mEstimatedVCardTypes[i] = detector.getEstimatedType();
+                    mEstimatedCharsets[i] = detector.getEstimatedCharset();
+                    mShouldUseV30[i] = shouldUseV30;
+                    mEntryCounts[i] = counter.getCount();
+                }
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
+                return false;
+            } catch (VCardNestedException e) {
+                Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
+            } catch (VCardNotSupportedException e) {
+                return false;
+            } catch (VCardException e) {
+                return false;
+            }
+            return true;
+        }
+
+        private void copyVCardToLocal() throws IOException {
+            final Context context = ImportVCardActivity.this;
+            final ContentResolver resolver = context.getContentResolver();
+            ReadableByteChannel inputChannel = null;
+            WritableByteChannel outputChannel = null;
+            try {
+                int length = mSourceUris.length;
+                for (int i = 0; i < length; i++) {
+                    if (mCanceled) {
+                        Log.d(LOG_TAG, "Canceled during import");
+                        break;
+                    }
+                    // XXX: better way to copy stream?
+                    inputChannel = Channels.newChannel(resolver.openInputStream(mSourceUris[i]));
+                    final String filename = CACHE_FILE_PREFIX + i + ".vcf"; 
+                    mDestUriStrings[i] =
+                            context.getFileStreamPath(filename).toURI().toString();
+                    Log.d("@@@", "temporary file: " + filename + ", dest: " + mDestUriStrings[i]);
+                    outputChannel =
+                            context.openFileOutput(filename, Context.MODE_PRIVATE).getChannel();
+                    {
+                        final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
+                        while (inputChannel.read(buffer) != -1) {
+                            if (mCanceled) {
+                                Log.d(LOG_TAG, "Canceled during caching " + mSourceUris[i]);
+                                break;
+                            }
+                            buffer.flip();
+                            outputChannel.write(buffer);
+                            buffer.compact();
+                        }
+                        buffer.flip();
+                        while (buffer.hasRemaining()) {
+                            outputChannel.write(buffer);
+                        }
+                    }
+
+                    // Avoid double close() in the "finally" block bellow. 
+                    Channel tmp = inputChannel;
+                    inputChannel = null;
+                    tmp.close();
+                    tmp = outputChannel;
+                    outputChannel = null;
+                    tmp.close();
+                }
+            } finally {
+                if (inputChannel != null) {
+                    try {
+                        inputChannel.close();
+                    } catch (IOException e) {
+                        Log.w(LOG_TAG, "Failed to close inputChannel.");
+                    }
+                }
+                if (outputChannel != null) {
+                    try {
+                        outputChannel.close();
+                    } catch(IOException e) {
+                        Log.w(LOG_TAG, "Failed to close outputChannel");
+                    }
+                }
+            }
+        }
+
+        /**
+         * We (currently) don't have any way to clean up cache files used in the previous
+         * import process,
+         * TODO(dmiyakawa): Can we do it after Service being done?
+         */
+        private void clearOldCache() {
+            final Context context = ImportVCardActivity.this;
+            final String[] fileLists = context.fileList();
+            for (String fileName : fileLists) {
+                if (fileName.startsWith(CACHE_FILE_PREFIX)) {
+                    Log.d(LOG_TAG, "Remove temporary file: " + fileName);
+                    context.deleteFile(fileName);
+                }
+            }
+        }
+        
+        public void cancel() {
+            mCanceled = true;
+            if (mVCardParser != null) {
+                mVCardParser.cancel();
+            }
+        }
+
+        public void onCancel(DialogInterface dialog) {
+            cancel();
+        }
+    }
+    
     private class ImportTypeSelectedListener implements
             DialogInterface.OnClickListener {
         public static final int IMPORT_ONE = 0;
@@ -366,16 +673,17 @@
     }
 
     private void importVCard(final String[] uriStrings) {
-        final Intent intent = new Intent(this, ImportVCardService.class);
-        intent.putExtra(VCARD_URI_ARRAY, uriStrings);
-        intent.putExtra(SelectAccountActivity.ACCOUNT_NAME, mAccountName);
-        intent.putExtra(SelectAccountActivity.ACCOUNT_TYPE, mAccountType);
-
-        // TODO: permission is not migrated to ImportVCardService, so some exception is
-        // thrown when reading some Uri, permission of which is temporarily guaranteed
-        // to ImportVCardActivity, not ImportVCardService.
-        startService(intent);
-        finish();
+        final int length = uriStrings.length;
+        final Uri[] uris = new Uri[length];
+        for (int i = 0; i < length; i++) {
+            uris[i] = Uri.parse(uriStrings[i]);
+        }
+        runOnUIThread(new Runnable() {
+            public void run() {
+                mVCardCacheThread = new VCardCacheThread(uris);
+                showDialog(R.id.dialog_cache_vcard);
+            }
+        });
     }
 
     private Dialog getSelectImportTypeDialog() {
@@ -543,6 +851,19 @@
             case R.id.dialog_select_one_vcard: {
                 return getVCardFileSelectDialog(false);
             }
+            case R.id.dialog_cache_vcard: {
+                if (mProgressDialogForCacheVCard == null) {
+                    final String title = getString(R.string.caching_vcard_title);
+                    final String message = getString(R.string.caching_vcard_message);
+                    mProgressDialogForCacheVCard = new ProgressDialog(this);
+                    mProgressDialogForCacheVCard.setTitle(title);
+                    mProgressDialogForCacheVCard.setMessage(message);
+                    mProgressDialogForCacheVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+                    mProgressDialogForCacheVCard.setOnCancelListener(mVCardCacheThread);
+                    mVCardCacheThread.start();
+                }
+                return mProgressDialogForCacheVCard;
+            }
             case R.id.dialog_io_exception: {
                 String message = (getString(R.string.scanning_sdcard_failed_message,
                         getString(R.string.fail_reason_io_error)));
diff --git a/src/com/android/contacts/ImportVCardService.java b/src/com/android/contacts/ImportVCardService.java
index 1482969..2be6468 100644
--- a/src/com/android/contacts/ImportVCardService.java
+++ b/src/com/android/contacts/ImportVCardService.java
@@ -141,7 +141,11 @@
                 mCreatedUris.clear();
 
                 final Account account;
-                final Uri[] uris;
+                final Uri uri;
+                final int estimatedType;
+                final String estimatedCharset;
+                final boolean useV30;
+                final int entryCount;
                 final int id;
                 final boolean needReview;
                 synchronized (mContext) {
@@ -151,97 +155,59 @@
                     } else {
                         final PendingInput pendingInput = mPendingInputs.poll();
                         account = pendingInput.account;
-                        uris = pendingInput.uris;
+                        uri = pendingInput.uri;
+                        estimatedType = pendingInput.estimatedType;
+                        estimatedCharset = pendingInput.estimatedCharset;
+                        useV30 = pendingInput.useV30;
+                        entryCount = pendingInput.entryCount;
                         id = pendingInput.id;
                     }
                 }
-                runInternal(account, uris, id);
-                doFinishNotification(id, uris);
+                runInternal(account, uri, estimatedType, estimatedCharset,
+                        useV30, entryCount, id);
+                doFinishNotification(id, uri);
             }
             Log.i(LOG_TAG, "Successfully imported. Total: " + mTotalCount);
             stopSelf();
         }
 
-        private void runInternal(Account account, Uri[] uris, int id) {
+        private void runInternal(Account account,
+                Uri uri, int estimatedType, String estimatedCharset,
+                boolean useV30, int entryCount,
+                int id) {
             int totalCount = 0;
             final ArrayList<VCardSourceDetector> detectorList =
                 new ArrayList<VCardSourceDetector>();
-            // First scan all Uris with a default charset and try to understand an exact
-            // charset to be used to each Uri. Note that detector would return null when
-            // it does not know an appropriate charset, so stick to use the default
-            // at that time.
-            // TODO: notification for first scanning?
-            for (Uri uri : uris) {
-                if (mCanceled) {
-                    return;
-                }
-                final VCardEntryCounter counter = new VCardEntryCounter();
-                final VCardSourceDetector detector = new VCardSourceDetector();
-                final VCardInterpreterCollection interpreterCollection =
-                        new VCardInterpreterCollection(Arrays.asList(counter, detector));
-                if (!readOneVCard(uri, VCardConfig.VCARD_TYPE_UNKNOWN, null,
-                        interpreterCollection)) {
-                    mErrorUris.add(uri);
-                }
-
-                totalCount += counter.getCount();
-                detectorList.add(detector);
-            }
-
-            if (mErrorUris.size() > 0) {
-                final StringBuilder builder = new StringBuilder();
-                builder.append("Error happened on ");
-                for (Uri errorUri : mErrorUris) {
-                    builder.append("\"");
-                    builder.append(errorUri.toString());
-                    builder.append("\"");
-                }
-                Log.e(LOG_TAG, builder.toString());
-                doErrorNotification(id);
-                return;
-            }
-
-            if (uris.length != detectorList.size()) {
-                Log.e(LOG_TAG,
-                        "The number of Uris to be imported is different from that of " +
-                        "charset to be used.");
-                doErrorNotification(id);
+            if (mCanceled) {
                 return;
             }
 
             // First scanning is over. Try to import each vCard, which causes side effects.
-            mTotalCount = totalCount;
+            mTotalCount += entryCount;
             mCurrentCount = 0;
 
-            for (int i = 0; i < uris.length; i++) {
-                if (mCanceled) {
-                    Log.w(LOG_TAG, "Canceled during importing (with storing data in database)");
-                    // TODO: implement cancel correctly.
-                    return;
-                }
-                final Uri uri = uris[i];
+            if (mCanceled) {
+                Log.w(LOG_TAG, "Canceled during importing (with storing data in database)");
+                // TODO: implement cancel correctly.
+                return;
+            }
 
-                final VCardSourceDetector detector = detectorList.get(i);
-                final int vcardType =  detector.getEstimatedType();  
-                final String charset = detector.getEstimatedCharset();  // May be null.
+            final VCardEntryConstructor constructor =
+                    new VCardEntryConstructor(estimatedType, account, estimatedCharset);
+            final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
+            constructor.addEntryHandler(committer);
+            constructor.addEntryHandler(new ProgressNotifier(id));
 
-                final VCardEntryConstructor constructor =
-                        new VCardEntryConstructor(vcardType, account, charset);
-                final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
-                constructor.addEntryHandler(committer);
-                constructor.addEntryHandler(new ProgressNotifier(id));
-
-                if (!readOneVCard(uri, vcardType, charset, constructor)) {
-                        Log.e(LOG_TAG, "Failed to read \"" + uri.toString() + "\" " +
-                                "while first scan was successful.");
-                }
+            if (!readOneVCard(uri, estimatedType, estimatedCharset, constructor)) {
+                Log.e(LOG_TAG, "Failed to read \"" + uri.toString() + "\" " +
+                "while first scan was successful.");
+            }
                 final List<Uri> createdUris = committer.getCreatedUris();
                 if (createdUris != null && createdUris.size() > 0) {
                     mCreatedUris.addAll(createdUris);
                 } else {
                     Log.w(LOG_TAG, "Created Uris is null (src = " + uri.toString() + "\"");
                 }
-            }
         }
 
         private boolean readOneVCard(Uri uri, int vcardType, String charset,
@@ -330,7 +296,7 @@
             mNotificationManager.notify(id, notification);
         }
 
-        private void doFinishNotification(int id, Uri[] uris) {
+        private void doFinishNotification(int id, Uri uri) {
             final Notification notification = new Notification();
             notification.icon = android.R.drawable.stat_sys_download_done;
             final String title = mContext.getString(R.string.reading_vcard_finished_title);
@@ -342,10 +308,9 @@
                             RawContacts.CONTENT_URI, rawContactId));
             intent = new Intent(Intent.ACTION_VIEW, contactUri);
 
-            final String text = ((uris.length == 1) ? uris[0].getPath() : "");
             final PendingIntent pendingIntent =
                     PendingIntent.getActivity(mContext, 0, intent, 0);
-            notification.setLatestEventInfo(mContext, title, text, pendingIntent);
+            notification.setLatestEventInfo(mContext, title, "", pendingIntent);
             mNotificationManager.notify(id, notification);
         }
 
@@ -364,17 +329,29 @@
 
     private static class PendingInput {
         public final Account account;
-        public final Uri[] uris;
+        public final Uri uri;
+        public final int estimatedType;
+        public final String estimatedCharset;
+        public final boolean useV30;
+        public final int entryCount;
         public final int id;
 
-        public PendingInput(Account account, Uri[] uris, int id) {
+        public PendingInput(Account account,
+                Uri uri, int estimatedType, String estimatedCharset,
+                boolean useV30, int entryCount,   
+                int id) {
             this.account = account;
-            this.uris = uris;
+            this.uri = uri;
+            this.estimatedType = estimatedType;
+            this.estimatedCharset = estimatedCharset;
+            this.useV30 = useV30;
+            this.entryCount = entryCount;
             this.id = id;
         }
     }
 
-    // The two classes bellow must be called inside the synchronized block, using this context.
+    // The two classes bellow must be called inside the synchronized block, using this
+    // Activity as a Context.
     private boolean mNowRunning;
     private final Queue<PendingInput> mPendingInputs = new LinkedList<PendingInput>();
 
@@ -423,6 +400,16 @@
                     (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
         }
 
+        // TODO: use this.
+        final int[] estimatedTypeArray =
+            intent.getIntArrayExtra(ImportVCardActivity.ESTIMATED_VCARD_TYPE_ARRAY);
+        final String[] estimatedCharsetArray =
+            intent.getStringArrayExtra(ImportVCardActivity.ESTIMATED_CHARSET_ARRAY);
+        final boolean[] useV30Array =
+            intent.getBooleanArrayExtra(ImportVCardActivity.USE_V30_ARRAY);
+        final int[] entryCountArray =
+            intent.getIntArrayExtra(ImportVCardActivity.ENTRY_COUNT_ARRAY);
+        
         final Account account = tryGetAccount(intent);
         final Uri[] uris = tryGetUris(intent);
         if (uris == null) {
@@ -433,8 +420,30 @@
             return START_NOT_STICKY;
         }
 
+        int length = uris.length;
+        if (estimatedTypeArray.length < length) {
+            Log.w(LOG_TAG, String.format("estimatedTypeArray.length < length (%d, %d)",
+                    estimatedTypeArray.length, length));
+            length = estimatedTypeArray.length;
+        }
+        if (useV30Array.length < length) {
+            Log.w(LOG_TAG, String.format("useV30Array.length < length (%d, %d)",
+                    useV30Array.length, length));
+            length = useV30Array.length;
+        }
+        if (entryCountArray.length < length) {
+            Log.w(LOG_TAG, String.format("entryCountArray.length < length (%d, %d)",
+                    entryCountArray.length, length));
+            length = entryCountArray.length;
+        }
+
         synchronized (this) {
-            mPendingInputs.add(new PendingInput(account, uris, startId));
+            for (int i = 0; i < length; i++) {
+                mPendingInputs.add(new PendingInput(account,
+                        uris[i], estimatedTypeArray[i], estimatedCharsetArray[i],
+                        useV30Array[i], entryCountArray[i],
+                        startId));
+            }
             if (!mNowRunning) {
                 Toast.makeText(this, getString(R.string.vcard_importer_start_message),
                         Toast.LENGTH_LONG).show();