Hand merge from cupcake_dcm from donut, part 4.

Make Contacts app use vCard importer.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 71a90eb..f515434 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
     <uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.mail" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <application
         android:label="@string/contactsList"
diff --git a/res/values/config.xml b/res/values/config.xml
new file mode 100644
index 0000000..9747fd1
--- /dev/null
+++ b/res/values/config.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<resources>
+    <!-- Flag indicating whether Contacts app is allowed to import contacts from SDCard -->
+    <bool name="config_allow_import_from_sd_card">false</bool>
+    <!-- If true, all vcard files are imported from SDCard without asking a user.
+    If not, dialog shows to let the user to select whether all vcard files are imported or not.
+    If the user selects "not", then the application ask the user to select a file.-->
+    <bool name="config_import_all_vcard_from_sdcard_automatically">false</bool>
+    <!-- If true, vcard importer shows a dialog which asks the user whether the user wants
+    to import all vcard files in SDCard or select one vcard file. If false, the dialog is
+    skipped and the importer asks the user to choose one vcard file.
+    If config_import_all_vcard_from_sdcard_automatically is set true, this configuration
+    is ignored. -->
+    <bool name="config_allow_users_select_all_vcard_import">false</bool>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3da39d9..c147479 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -582,4 +582,75 @@
     -->
     <string name="description_image_button_pound">pound</string>
 
+    <!-- Dialog title shown when SD Card does not exist -->
+    <string name="no_sdcard_title">No SD Card</string>
+
+    <!-- Dialog message shown when SDcard does not exist -->
+    <string name="no_sdcard_message">No SD Card detected</string>
+
+    <!-- Dialog title shown when searching VCard data from SD Card -->
+    <string name="searching_vcard_title">Searching for VCard</string>
+
+    <!-- Dialog title shown when asking a user whether import contact data from SIM or SD Card -->
+    <string name="select_import_type_title">Where would you like to import contacts from?</string>
+
+    <!-- Action string for selecting SIM for importing contacts -->
+    <string name="import_from_sim">SIM Card</string>
+
+    <!-- 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>
+
+    <!-- Dialog message shown when searching VCard data from SD Card -->
+    <string name="searching_vcard_message">Searching for VCard data on VCard</string>
+
+    <!-- Dialog title shown when searching VCard data failed. -->
+    <string name="scanning_sdcard_failed_title">Scanning SD Card failed</string>
+
+    <!-- Dialog message shown when searching VCard data failed. -->
+    <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>
+
+    <!-- The failed reason: "Failed to parse VCard data" -->
+    <string name="fail_reason_vcard_parse_error">Failed to parse VCard</string>
+
+    <!-- The failed reason: "There is no VCard file" -->
+    <string name="fail_reason_no_vcard_file">No VCard file found on SD Card</string>
+
+    <!-- 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>
+
+    <!-- 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>
+
+    <!-- Dialog title shown when reading VCard data -->
+    <string name="reading_vcard_title">Reading VCard</string>
+
+    <!-- Dialog message shown when reading a VCard file -->
+    <string name="reading_vcard_message">Reading VCard file(s)</string>
+
+    <!-- Dialog message shown when importing VCard data into local database -->
+    <string name="importing_vcard_message">Importing VCard data</string>
+
+    <!-- Dialog title shown when reading VCard data failed -->
+    <string name="reading_vcard_failed_title">Reading of VCard data has failed</string>
+
+    <!-- Dialog message shown when reading VCard data failed -->
+    <string name="reading_vcard_failed_message">VCard data could not be read\nReason for failure: \"<xliff:g id="fail_reason">%s</xliff:g>\"</string>
+
+    <!-- Message while reading one vCard file "(current number) of (total number) contacts" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
+    <string name="reading_vcard_contacts"><xliff:g id="current_number">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> contacts</string>
+
+    <!-- Message while reading multiple vCard files "(current number) of (total number) files" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
+    <string name="reading_vcard_files"><xliff:g id="current_number">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> files</string>
 </resources>
diff --git a/src/com/android/contacts/ContactsListActivity.java b/src/com/android/contacts/ContactsListActivity.java
index 7dbc76c..271a6b3 100644
--- a/src/com/android/contacts/ContactsListActivity.java
+++ b/src/com/android/contacts/ContactsListActivity.java
@@ -16,8 +16,6 @@
 
 package com.android.contacts;
 
-import static com.android.contacts.ShowOrCreateActivity.QUERY_KIND_EMAIL_OR_IM;
-
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.ListActivity;
@@ -34,11 +32,11 @@
 import android.content.SharedPreferences;
 import android.database.CharArrayBuffer;
 import android.database.Cursor;
-import android.database.CursorWrapper;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.preference.PreferenceManager;
@@ -101,6 +99,7 @@
     public static final int MENU_DIALER = 9;
     public static final int MENU_NEW_CONTACT = 10;
     public static final int MENU_DISPLAY_GROUP = 11;
+    public static final int MENU_IMPORT_CONTACTS = 12;
 
     private static final int SUBACTIVITY_NEW_CONTACT = 1;
     
@@ -310,6 +309,34 @@
      */
     private String mQueryData;
     
+    private Handler mHandler = new Handler();
+
+    private class ImportTypeSelectedListener implements DialogInterface.OnClickListener {
+        public static final int IMPORT_FROM_SIM = 0;
+        public static final int IMPORT_FROM_SDCARD = 1;
+
+        private int mIndex;
+
+        public ImportTypeSelectedListener() {
+            mIndex = IMPORT_FROM_SIM;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                if (mIndex == IMPORT_FROM_SIM) {
+                    doImportFromSim();
+                } else {
+                    VCardImporter importer = new VCardImporter(ContactsListActivity.this, mHandler);
+                    importer.startImportVCardFromSdCard();
+                }
+            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+
+            } else {
+                mIndex = which;
+            }
+        }
+    }
+
     private class DeleteClickListener implements DialogInterface.OnClickListener {
         private Uri mUri;
 
@@ -746,13 +773,9 @@
                     .setIntent(syncIntent);
         }
         
-        // SIM import
-        Intent importIntent = new Intent(Intent.ACTION_VIEW);
-        importIntent.setType("vnd.android.cursor.item/sim-contact");
-        importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
-        menu.add(0, 0, 0, R.string.importFromSim)
-                .setIcon(R.drawable.ic_menu_import_contact)
-                .setIntent(importIntent);
+        // Contacts import (SIM/SDCard)
+        menu.add(0, MENU_IMPORT_CONTACTS, 0, R.string.importFromSim)
+                .setIcon(R.drawable.ic_menu_import_contact);
 
         return super.onCreateOptionsMenu(menu);
     }
@@ -816,10 +839,36 @@
             case MENU_SEARCH:
                 startSearch(null, false, null, false);
                 return true;
+
+            case MENU_IMPORT_CONTACTS:
+                if (getResources().getBoolean(R.bool.config_allow_import_from_sd_card)) {
+                    ImportTypeSelectedListener listener =
+                        new ImportTypeSelectedListener();
+                    AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
+                        .setTitle(R.string.select_import_type_title)
+                        .setPositiveButton(android.R.string.ok, listener)
+                        .setNegativeButton(android.R.string.cancel, null);
+                    dialogBuilder.setSingleChoiceItems(new String[] {
+                            getString(R.string.import_from_sim),
+                            getString(R.string.import_from_sdcard)},
+                            ImportTypeSelectedListener.IMPORT_FROM_SIM, listener);
+                    dialogBuilder.show();
+                } else {
+                    doImportFromSim();
+                }
+                return true;
+
         }
         return false;
     }
 
+    private void doImportFromSim() {
+        Intent importIntent = new Intent(Intent.ACTION_VIEW);
+        importIntent.setType("vnd.android.cursor.item/sim-contact");
+        importIntent.setClassName("com.android.phone", "com.android.phone.SimContacts");
+        startActivity(importIntent);
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode,
             Intent data) {
@@ -1461,7 +1510,7 @@
         }
 
         private SectionIndexer getNewIndexer(Cursor cursor) {
-            if (Locale.getDefault().equals(Locale.JAPAN)) {
+            if (Locale.getDefault().getLanguage().equals(Locale.JAPAN.getLanguage())) {
                 return new JapaneseContactListIndexer(cursor, SORT_STRING_INDEX);
             } else {
                 return new AlphabetIndexer(cursor, NAME_COLUMN_INDEX, mAlphabet);
diff --git a/src/com/android/contacts/VCardImporter.java b/src/com/android/contacts/VCardImporter.java
new file mode 100644
index 0000000..e987e85
--- /dev/null
+++ b/src/com/android/contacts/VCardImporter.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.syncml.pim.VBuilder;
+import android.syncml.pim.VBuilderCollection;
+import android.syncml.pim.VParser;
+import android.syncml.pim.vcard.VCardDataBuilder;
+import android.syncml.pim.vcard.VCardEntryCounter;
+import android.syncml.pim.vcard.VCardException;
+import android.syncml.pim.vcard.VCardNestedException;
+import android.syncml.pim.vcard.VCardParser_V21;
+import android.syncml.pim.vcard.VCardParser_V30;
+import android.syncml.pim.vcard.VCardSourceDetector;
+import android.syncml.pim.vcard.VCardVersionException;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.RelativeSizeSpan;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
+
+class VCardFile {
+    private String mName;
+    private String mCanonicalPath;
+    private long mLastModified;
+
+    public VCardFile(String name, String canonicalPath, long lastModified) {
+        mName = name;
+        mCanonicalPath = canonicalPath;
+        mLastModified = lastModified;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getCanonicalPath() {
+        return mCanonicalPath;
+    }
+
+    public long getLastModified() {
+        return mLastModified;
+    }
+}
+
+/**
+ * Class for importing vCard. Several user interaction will be required while reading
+ * (selecting a file, waiting a moment, etc.)
+ */
+public class VCardImporter {
+    private static final String LOG_TAG = "VCardImporter";
+    private static final boolean DO_PERFORMANCE_PROFILE = false;
+
+    private ProgressDialog mProgressDialog;
+    private Context mParentContext;
+    private Handler mParentHandler;
+    private boolean mLastNameComesBeforeFirstName;
+
+    private class ErrorDisplayer implements Runnable {
+        private String mErrorMessage;
+
+        public ErrorDisplayer(String errorMessage) {
+            mErrorMessage = errorMessage;
+        }
+
+        public void run() {
+            String message =
+                getString(R.string.reading_vcard_failed_message, mErrorMessage);
+            AlertDialog.Builder builder =
+                new AlertDialog.Builder(mParentContext)
+                    .setTitle(getString(R.string.reading_vcard_failed_title))
+                    .setIcon(android.R.drawable.ic_dialog_alert)
+                    .setMessage(message)
+                    .setPositiveButton(android.R.string.ok, null);
+            builder.show();
+        }
+    }
+
+    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;
+
+        public VCardReadThread(String canonicalPath) {
+            mCanonicalPath = canonicalPath;
+            mVCardFileList = null;
+            init();
+        }
+
+        public VCardReadThread(List<VCardFile> vcardFileList) {
+            mCanonicalPath = null;
+            mVCardFileList = vcardFileList;
+            init();
+        }
+
+        private void init() {
+            mResolver = mParentContext.getContentResolver();
+            PowerManager powerManager = (PowerManager)mParentContext.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() {
+            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) {
+                    mProgressDialog.setProgressNumberFormat("");
+                    mProgressDialog.setProgress(0);
+
+                    // Count the number of VCard entries
+                    mProgressDialog.setIndeterminate(true);
+                    long start;
+                    if (DO_PERFORMANCE_PROFILE) {
+                        start = System.currentTimeMillis();
+                    }
+                    VCardEntryCounter counter = new VCardEntryCounter();
+                    VCardSourceDetector detector = new VCardSourceDetector();
+                    VBuilderCollection builderCollection = new VBuilderCollection(
+                            Arrays.asList(counter, detector));
+                    boolean result;
+                    try {
+                        result = readOneVCard(mCanonicalPath,
+                                VParser.DEFAULT_CHARSET, builderCollection, null, true);
+                    } catch (VCardNestedException e) {
+                        try {
+                            // Assume that VCardSourceDetector was able to detect the source.
+                            // Try again with the detector.
+                            result = readOneVCard(mCanonicalPath,
+                                    VParser.DEFAULT_CHARSET, counter, detector, false);
+                        } catch (VCardNestedException e2) {
+                            result = false;
+                            Log.e(LOG_TAG, "Must not reach here. " + e2);
+                        }
+                    }
+                    if (DO_PERFORMANCE_PROFILE) {
+                        long time = System.currentTimeMillis() - start;
+                        Log.d(LOG_TAG, "time for counting the number of vCard entries: " +
+                                time + " ms");
+                    }
+                    if (!result) {
+                        return;
+                    }
+
+                    mProgressDialog.setProgressNumberFormat(
+                            getString(R.string.reading_vcard_contacts));
+                    mProgressDialog.setIndeterminate(false);
+                    mProgressDialog.setMax(counter.getCount());
+                    String charset = detector.getEstimatedCharset();
+                    doActuallyReadOneVCard(charset, true, detector);
+                } else {
+                    mProgressDialog.setProgressNumberFormat(
+                            getString(R.string.reading_vcard_files));
+                    mProgressDialog.setMax(mVCardFileList.size());
+                    mProgressDialog.setProgress(0);
+                    for (VCardFile vcardFile : mVCardFileList) {
+                        if (mCanceled) {
+                            return;
+                        }
+                        String canonicalPath = vcardFile.getCanonicalPath();
+
+                        VCardSourceDetector detector = new VCardSourceDetector();
+                        try {
+                            if (!readOneVCard(canonicalPath, VParser.DEFAULT_CHARSET, detector,
+                                    null, true)) {
+                                continue;
+                            }
+                        } catch (VCardNestedException e) {
+                            // Assume that VCardSourceDetector was able to detect the source.
+                        }
+                        String charset = detector.getEstimatedCharset();
+                        doActuallyReadOneVCard(charset, false, detector);
+                        mProgressDialog.incrementProgressBy(1);
+                    }
+                }
+            } finally {
+                mWakeLock.release();
+                mProgressDialog.dismiss();
+            }
+        }
+
+        private void doActuallyReadOneVCard(String charset, boolean doIncrementProgress,
+                VCardSourceDetector detector) {
+            VCardDataBuilder builder;
+            if (charset != null) {
+                builder = new VCardDataBuilder(mResolver,
+                        mProgressDialog,
+                        mParentContext.getString(R.string.reading_vcard_message),
+                        mParentHandler,
+                        charset,
+                        charset,
+                        false,
+                        mLastNameComesBeforeFirstName);
+            } else {
+                builder = new VCardDataBuilder(mResolver,
+                        mProgressDialog,
+                        mParentContext.getString(R.string.reading_vcard_message),
+                        mParentHandler,
+                        null,
+                        null,
+                        false,
+                        mLastNameComesBeforeFirstName);
+                charset = VParser.DEFAULT_CHARSET;
+            }
+            if (doIncrementProgress) {
+                builder.setOnProgressRunnable(new Runnable() {
+                    public void run() {
+                        mProgressDialog.incrementProgressBy(1);
+                    }
+                });
+            }
+            try {
+                readOneVCard(mCanonicalPath, charset, builder, detector, false);
+            } catch (VCardNestedException e) {
+                Log.e(LOG_TAG, "Must not reach here.");
+            }
+            builder.showDebugInfo();
+        }
+
+        private boolean readOneVCard(String canonicalPath, String charset, VBuilder builder,
+                VCardSourceDetector detector, boolean throwNestedException)
+                throws VCardNestedException {
+            FileInputStream is;
+            try {
+                is = new FileInputStream(canonicalPath);
+                mVCardParser = new VCardParser_V21(detector);
+
+                try {
+                    mVCardParser.parse(is, charset, builder, mCanceled);
+                } catch (VCardVersionException e1) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                    is = new FileInputStream(canonicalPath);
+
+                    try {
+                        mVCardParser = new VCardParser_V30();
+                        mVCardParser.parse(is, charset, builder, mCanceled);
+                    } catch (VCardVersionException e2) {
+                        throw new VCardException("vCard with unspported version.");
+                    }
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+                mVCardParser.showDebugInfo();
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "IOException was emitted: " + e);
+
+                mProgressDialog.dismiss();
+
+                mParentHandler.post(new ErrorDisplayer(
+                        getString(R.string.fail_reason_io_error) +
+                        " (" + e.getMessage() + ")"));
+                return false;
+            } catch (VCardNestedException e) {
+                if (throwNestedException) {
+                    throw e;
+                } else {
+                    Log.e(LOG_TAG, "VCardNestedException was emitted: " + e);
+                    mParentHandler.post(new ErrorDisplayer(
+                            getString(R.string.fail_reason_vcard_parse_error) +
+                            " (" + e.getMessage() + ")"));
+                    return false;
+                }
+            } catch (VCardException e) {
+                Log.e(LOG_TAG, "VCardException was emitted: " + e);
+
+                mParentHandler.post(new ErrorDisplayer(
+                        getString(R.string.fail_reason_vcard_parse_error) +
+                        " (" + e.getMessage() + ")"));
+                return false;
+            }
+            return true;
+        }
+
+        public void onCancel(DialogInterface dialog) {
+            mCanceled = true;
+            if (mVCardParser != null) {
+                mVCardParser.cancel();
+            }
+        }
+    }
+
+    private class ImportTypeSelectedListener implements
+            DialogInterface.OnClickListener {
+        public static final int IMPORT_ALL = 0;
+        public static final int IMPORT_ONE = 1;
+
+        private List<VCardFile> mVCardFileList;
+        private int mCurrentIndex;
+
+        public ImportTypeSelectedListener(List<VCardFile> vcardFileList) {
+            mVCardFileList = vcardFileList;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                if (mCurrentIndex == IMPORT_ALL) {
+                    importAllVCardFromSDCard(mVCardFileList);
+                } else {
+                    showVCardFileSelectDialog(mVCardFileList);
+                }
+            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+
+            } else {
+                mCurrentIndex = which;
+            }
+        }
+    }
+
+    private class VCardSelectedListener implements DialogInterface.OnClickListener {
+        private List<VCardFile> mVCardFileList;
+        private int mCurrentIndex;
+
+        public VCardSelectedListener(List<VCardFile> vcardFileList) {
+            mVCardFileList = vcardFileList;
+            mCurrentIndex = 0;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                importOneVCardFromSDCard(mVCardFileList.get(mCurrentIndex).getCanonicalPath());
+            } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+
+            } else {
+                // Some file is selected.
+                mCurrentIndex = which;
+            }
+        }
+    }
+
+    /**
+     * Thread scanning VCard from SDCard. After scanning, the dialog which lets a user select
+     * a vCard file is shown. After the choice, VCardReadThread starts running.
+     */
+    private class VCardScanThread extends Thread implements OnCancelListener, OnClickListener {
+        private boolean mCanceled;
+        private boolean mGotIOException;
+        private File mRootDirectory;
+
+        // null when search operation is canceled.
+        private List<VCardFile> mVCardFiles;
+
+        // To avoid recursive link.
+        private Set<String> mCheckedPaths;
+        private PowerManager.WakeLock mWakeLock;
+
+        private class CanceledException extends Exception {
+        }
+
+        public VCardScanThread(File sdcardDirectory) {
+            mCanceled = false;
+            mGotIOException = false;
+            mRootDirectory = sdcardDirectory;
+            mCheckedPaths = new HashSet<String>();
+            mVCardFiles = new Vector<VCardFile>();
+            PowerManager powerManager = (PowerManager)mParentContext.getSystemService(
+                    Context.POWER_SERVICE);
+            mWakeLock = powerManager.newWakeLock(
+                    PowerManager.SCREEN_DIM_WAKE_LOCK |
+                    PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+        }
+
+        @Override
+        public void run() {
+            try {
+                mWakeLock.acquire();
+                getVCardFileRecursively(mRootDirectory);
+            } catch (CanceledException e) {
+                mCanceled = true;
+            } catch (IOException e) {
+                mGotIOException = true;
+            } finally {
+                mWakeLock.release();
+            }
+
+            if (mCanceled) {
+                mVCardFiles = null;
+            }
+
+            mProgressDialog.dismiss();
+
+            if (mGotIOException) {
+                mParentHandler.post(new Runnable() {
+                    public void run() {
+                        String message = (getString(R.string.scanning_sdcard_failed_message,
+                                getString(R.string.fail_reason_io_error)));
+
+                        AlertDialog.Builder builder =
+                            new AlertDialog.Builder(mParentContext)
+                                .setTitle(R.string.scanning_sdcard_failed_title)
+                                .setIcon(android.R.drawable.ic_dialog_alert)
+                                .setMessage(message)
+                                .setPositiveButton(android.R.string.ok, null);
+                        builder.show();
+                    }
+                });
+            } else if (mCanceled) {
+                return;
+            } else {
+                mParentHandler.post(new Runnable() {
+                    public void run() {
+                        int size = mVCardFiles.size();
+                        if (size == 0) {
+                            String message = (getString(R.string.scanning_sdcard_failed_message,
+                                    getString(R.string.fail_reason_no_vcard_file)));
+
+                            AlertDialog.Builder builder =
+                                new AlertDialog.Builder(mParentContext)
+                                    .setTitle(R.string.scanning_sdcard_failed_title)
+                                    .setMessage(message)
+                                    .setPositiveButton(android.R.string.ok, null);
+                            builder.show();
+                            return;
+                        } else if (mParentContext.getResources().getBoolean(
+                                R.bool.config_import_all_vcard_from_sdcard_automatically)) {
+                            importAllVCardFromSDCard(mVCardFiles);
+                        } else if (size == 1) {
+                            importOneVCardFromSDCard(mVCardFiles.get(0).getCanonicalPath());
+                        } else if (mParentContext.getResources().getBoolean(
+                                R.bool.config_allow_users_select_all_vcard_import)) {
+                            showSelectImportTypeDialog(mVCardFiles);
+                        } else {
+                            showVCardFileSelectDialog(mVCardFiles);
+                        }
+                    }
+                });
+            }
+        }
+
+        private void getVCardFileRecursively(File directory)
+                throws CanceledException, IOException {
+            if (mCanceled) {
+                throw new CanceledException();
+            }
+
+            for (File file : directory.listFiles()) {
+                if (mCanceled) {
+                    throw new CanceledException();
+                }
+                String canonicalPath = file.getCanonicalPath();
+                if (mCheckedPaths.contains(canonicalPath)) {
+                    continue;
+                }
+
+                mCheckedPaths.add(canonicalPath);
+
+                if (file.isDirectory()) {
+                    getVCardFileRecursively(file);
+                } else if (canonicalPath.toLowerCase().endsWith(".vcf") &&
+                        file.canRead()){
+                    String fileName = file.getName();
+                    VCardFile vcardFile = new VCardFile(
+                            fileName, canonicalPath, file.lastModified());
+                    mVCardFiles.add(vcardFile);
+                }
+            }
+        }
+
+        public void onCancel(DialogInterface dialog) {
+            mCanceled = true;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_NEGATIVE) {
+                mCanceled = true;
+            }
+        }
+    }
+
+    
+    private void importOneVCardFromSDCard(String canonicalPath) {
+        VCardReadThread thread = new VCardReadThread(canonicalPath);
+        showReadingVCardDialog(thread);
+        thread.start();
+    }
+
+    private void importAllVCardFromSDCard(List<VCardFile> vcardFileList) {
+        VCardReadThread thread = new VCardReadThread(vcardFileList);
+        showReadingVCardDialog(thread);
+        thread.start();
+    }
+
+    private void showSelectImportTypeDialog(List<VCardFile> vcardFileList) {
+        DialogInterface.OnClickListener listener =
+            new ImportTypeSelectedListener(vcardFileList);
+        AlertDialog.Builder builder =
+            new AlertDialog.Builder(mParentContext)
+                .setTitle(R.string.select_vcard_title)
+                .setPositiveButton(android.R.string.ok, listener)
+                .setNegativeButton(android.R.string.cancel, null);
+
+        String[] items = new String[2];
+        items[ImportTypeSelectedListener.IMPORT_ALL] =
+            getString(R.string.import_all_vcard_string);
+        items[ImportTypeSelectedListener.IMPORT_ONE] =
+            getString(R.string.import_one_vcard_string);
+        builder.setSingleChoiceItems(items,
+                ImportTypeSelectedListener.IMPORT_ALL, listener);
+        builder.show();
+    }
+
+    private void showVCardFileSelectDialog(List<VCardFile> vcardFileList) {
+        int size = vcardFileList.size();
+        DialogInterface.OnClickListener listener =
+            new VCardSelectedListener(vcardFileList);
+        AlertDialog.Builder builder =
+            new AlertDialog.Builder(mParentContext)
+                .setTitle(R.string.select_vcard_title)
+                .setPositiveButton(android.R.string.ok, listener)
+                .setNegativeButton(android.R.string.cancel, null);
+
+        CharSequence[] items = new CharSequence[size];
+        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        for (int i = 0; i < size; i++) {
+            VCardFile vcardFile = vcardFileList.get(i);
+            SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
+            stringBuilder.append(vcardFile.getName());
+            stringBuilder.append('\n');
+            int indexToBeSpanned = stringBuilder.length();
+            // Smaller date text looks better, since each file name becomes easier to read.
+            // The value set to RelativeSizeSpan is arbitrary. You can change it to any other
+            // value (but the value bigger than 1.0f would not make nice appearance :)
+            stringBuilder.append(
+                        "(" + dateFormat.format(new Date(vcardFile.getLastModified())) + ")");
+            stringBuilder.setSpan(
+                    new RelativeSizeSpan(0.7f), indexToBeSpanned, stringBuilder.length(),
+                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            items[i] = stringBuilder;
+        }
+        builder.setSingleChoiceItems(items, 0, listener);
+        builder.show();
+    }
+
+    private void showReadingVCardDialog(DialogInterface.OnCancelListener listener) {
+        String title = getString(R.string.reading_vcard_title);
+        String message = getString(R.string.reading_vcard_message);
+        mProgressDialog = new ProgressDialog(mParentContext);
+        mProgressDialog.setTitle(title);
+        mProgressDialog.setMessage(message);
+        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+        mProgressDialog.setOnCancelListener(listener);
+        mProgressDialog.show();
+    }
+
+    private String getString(int resId, Object... formatArgs) {
+        return mParentContext.getString(resId, formatArgs);
+    }
+
+    private String getString(int resId) {
+        return mParentContext.getString(resId);
+    }
+
+    /**
+     * @param parentContext must not be null
+     * @param parentHandler must not be null
+     */
+    public VCardImporter(Context parentContext, Handler parentHandler) {
+        mParentContext = parentContext;
+        mParentHandler = parentHandler;
+        mLastNameComesBeforeFirstName = parentContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_lastname_comes_before_firstname);
+    }
+
+    /**
+     * Tries to start importing VCard. If there's no SDCard available,
+     * an error dialog is shown. If there is, start scanning using another thread
+     * and shows a progress dialog. Several interactions will occur.
+     * This method should be called from a thread with a looper (like Activity).
+     */
+    public void startImportVCardFromSdCard() {
+        File file = new File("/sdcard");
+        if (!file.exists() || !file.isDirectory() || !file.canRead()) {
+            new AlertDialog.Builder(mParentContext)
+                    .setTitle(R.string.no_sdcard_title)
+                    .setIcon(android.R.drawable.ic_dialog_alert)
+                    .setMessage(R.string.no_sdcard_message)
+                    .setPositiveButton(android.R.string.ok, null)
+                    .show();
+        } else {
+            String title = getString(R.string.searching_vcard_title);
+            String message = getString(R.string.searching_vcard_message);
+
+            mProgressDialog = ProgressDialog.show(mParentContext,
+                    title, message, true, false);
+            VCardScanThread thread = new VCardScanThread(file);
+            mProgressDialog.setOnCancelListener(thread);
+            thread.start();
+        }
+    }
+}