OSDN Git Service

8fd9c0d81d674bc23370c716bac5dbe454cbebac
[android-x86/packages-apps-Contacts.git] / src / com / android / contacts / ImportVCardActivity.java
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.contacts;
18
19 import android.accounts.Account;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.app.Dialog;
23 import android.app.ProgressDialog;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.DialogInterface.OnCancelListener;
29 import android.content.DialogInterface.OnClickListener;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.PowerManager;
33 import android.pim.vcard.EntryCommitter;
34 import android.pim.vcard.VCardBuilder;
35 import android.pim.vcard.VCardBuilderCollection;
36 import android.pim.vcard.VCardConfig;
37 import android.pim.vcard.VCardDataBuilder;
38 import android.pim.vcard.VCardEntryCounter;
39 import android.pim.vcard.VCardParser_V21;
40 import android.pim.vcard.VCardParser_V30;
41 import android.pim.vcard.VCardSourceDetector;
42 import android.pim.vcard.exception.VCardException;
43 import android.pim.vcard.exception.VCardNestedException;
44 import android.pim.vcard.exception.VCardNotSupportedException;
45 import android.pim.vcard.exception.VCardVersionException;
46 import android.text.SpannableStringBuilder;
47 import android.text.Spanned;
48 import android.text.TextUtils;
49 import android.text.style.RelativeSizeSpan;
50 import android.util.Log;
51
52 import java.io.File;
53 import java.io.FileInputStream;
54 import java.io.IOException;
55 import java.text.DateFormat;
56 import java.text.SimpleDateFormat;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Date;
60 import java.util.HashSet;
61 import java.util.List;
62 import java.util.Locale;
63 import java.util.Set;
64 import java.util.Vector;
65
66 class VCardFile {
67     private String mName;
68     private String mCanonicalPath;
69     private long mLastModified;
70
71     public VCardFile(String name, String canonicalPath, long lastModified) {
72         mName = name;
73         mCanonicalPath = canonicalPath;
74         mLastModified = lastModified;
75     }
76
77     public String getName() {
78         return mName;
79     }
80
81     public String getCanonicalPath() {
82         return mCanonicalPath;
83     }
84
85     public long getLastModified() {
86         return mLastModified;
87     }
88 }
89
90 /**
91  * Class for importing vCard. Several user interaction will be required while reading
92  * (selecting a file, waiting a moment, etc.)
93  */
94 public class ImportVCardActivity extends Activity {
95     private static final String LOG_TAG = "ImportVCardActivity";
96     private static final boolean DO_PERFORMANCE_PROFILE = false;
97
98     private Handler mHandler = new Handler();
99     private Account mAccount;
100
101     private ProgressDialog mProgressDialogForScanVCard;
102
103     private List<VCardFile> mAllVCardFileList;
104     private VCardScanThread mVCardScanThread;
105     private VCardReadThread mVCardReadThread;
106     private ProgressDialog mProgressDialogForReadVCard;
107
108     private String mErrorMessage;
109
110     private class DialogDisplayer implements Runnable {
111         private final int mResId;
112         public DialogDisplayer(int resId) {
113             mResId = resId;
114         }
115         public DialogDisplayer(String errorMessage) {
116             mResId = R.id.dialog_error_with_message;
117             mErrorMessage = errorMessage;
118         }
119         public void run() {
120             // Show the Dialog only when the parent Activity is still alive.
121             if (!ImportVCardActivity.this.isFinishing()) {
122                 showDialog(mResId);
123             }
124         }
125     }
126
127     private class CancelListener
128         implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
129         public void onClick(DialogInterface dialog, int which) {
130             finish();
131         }
132
133         public void onCancel(DialogInterface dialog) {
134             finish();
135         }
136     }
137
138     private CancelListener mCancelListener = new CancelListener();
139
140     private class VCardReadThread extends Thread
141             implements DialogInterface.OnCancelListener {
142         private ContentResolver mResolver;
143         private VCardParser_V21 mVCardParser;
144         private boolean mCanceled;
145         private PowerManager.WakeLock mWakeLock;
146         private String mCanonicalPath;
147
148         private List<VCardFile> mSelectedVCardFileList;
149         private List<String> mErrorFileNameList;
150
151         public VCardReadThread(String canonicalPath) {
152             mCanonicalPath = canonicalPath;
153             init();
154         }
155
156         public VCardReadThread(final List<VCardFile> selectedVCardFileList) {
157             mCanonicalPath = null;
158             mSelectedVCardFileList = selectedVCardFileList;
159             mErrorFileNameList = new ArrayList<String>();
160             init();
161         }
162
163         private void init() {
164             Context context = ImportVCardActivity.this;
165             mResolver = context.getContentResolver();
166             PowerManager powerManager = (PowerManager)context.getSystemService(
167                     Context.POWER_SERVICE);
168             mWakeLock = powerManager.newWakeLock(
169                     PowerManager.SCREEN_DIM_WAKE_LOCK |
170                     PowerManager.ON_AFTER_RELEASE, LOG_TAG);
171         }
172
173         @Override
174         public void finalize() {
175             if (mWakeLock != null && mWakeLock.isHeld()) {
176                 mWakeLock.release();
177             }
178         }
179
180         @Override
181         public void run() {
182             boolean shouldCallFinish = true;
183             mWakeLock.acquire();
184             // Some malicious vCard data may make this thread broken
185             // (e.g. OutOfMemoryError).
186             // Even in such cases, some should be done.
187             try {
188                 if (mCanonicalPath != null) {  // Read one file
189                     mProgressDialogForReadVCard.setProgressNumberFormat("");
190                     mProgressDialogForReadVCard.setProgress(0);
191
192                     // Count the number of VCard entries
193                     mProgressDialogForReadVCard.setIndeterminate(true);
194                     long start;
195                     if (DO_PERFORMANCE_PROFILE) {
196                         start = System.currentTimeMillis();
197                     }
198                     VCardEntryCounter counter = new VCardEntryCounter();
199                     VCardSourceDetector detector = new VCardSourceDetector();
200                     VCardBuilderCollection builderCollection = new VCardBuilderCollection(
201                             Arrays.asList(counter, detector));
202
203                     boolean result;
204                     try {
205                         result = readOneVCardFile(mCanonicalPath,
206                                 VCardConfig.DEFAULT_CHARSET, builderCollection, null, true, null);
207                     } catch (VCardNestedException e) {
208                         try {
209                             // Assume that VCardSourceDetector was able to detect the source.
210                             // Try again with the detector.
211                             result = readOneVCardFile(mCanonicalPath,
212                                     VCardConfig.DEFAULT_CHARSET, counter, detector, false, null);
213                         } catch (VCardNestedException e2) {
214                             result = false;
215                             Log.e(LOG_TAG, "Must not reach here. " + e2);
216                         }
217                     }
218                     if (DO_PERFORMANCE_PROFILE) {
219                         long time = System.currentTimeMillis() - start;
220                         Log.d(LOG_TAG, "time for counting the number of vCard entries: " +
221                                 time + " ms");
222                     }
223                     if (!result) {
224                         shouldCallFinish = false;
225                         return;
226                     }
227
228                     mProgressDialogForReadVCard.setProgressNumberFormat(
229                             getString(R.string.reading_vcard_contacts));
230                     mProgressDialogForReadVCard.setIndeterminate(false);
231                     mProgressDialogForReadVCard.setMax(counter.getCount());
232                     String charset = detector.getEstimatedCharset();
233                     doActuallyReadOneVCard(mCanonicalPath, null, charset, true, detector,
234                             mErrorFileNameList);
235                 } else {  // Read multiple files.
236                     mProgressDialogForReadVCard.setProgressNumberFormat(
237                             getString(R.string.reading_vcard_files));
238                     mProgressDialogForReadVCard.setMax(mSelectedVCardFileList.size());
239                     mProgressDialogForReadVCard.setProgress(0);
240                     
241                     for (VCardFile vcardFile : mSelectedVCardFileList) {
242                         if (mCanceled) {
243                             return;
244                         }
245                         String canonicalPath = vcardFile.getCanonicalPath();
246
247                         VCardSourceDetector detector = new VCardSourceDetector();
248                         try {
249                             if (!readOneVCardFile(canonicalPath, VCardConfig.DEFAULT_CHARSET,
250                                     detector, null, true, mErrorFileNameList)) {
251                                 continue;
252                             }
253                         } catch (VCardNestedException e) {
254                             // Assume that VCardSourceDetector was able to detect the source.
255                         }
256                         String charset = detector.getEstimatedCharset();
257                         doActuallyReadOneVCard(canonicalPath, mAccount,
258                                 charset, false, detector, mErrorFileNameList);
259                         mProgressDialogForReadVCard.incrementProgressBy(1);
260                     }
261                 }
262             } finally {
263                 mWakeLock.release();
264                 mProgressDialogForReadVCard.dismiss();
265                 // finish() is called via mCancelListener, which is used in DialogDisplayer.
266                 if (shouldCallFinish && !isFinishing()) {
267                     if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
268                         finish();
269                     } else {
270                         StringBuilder builder = new StringBuilder();
271                         boolean first = true;
272                         for (String fileName : mErrorFileNameList) {
273                             if (first) {
274                                 first = false;
275                             } else {
276                                 builder.append(", ");
277                             }
278                             builder.append(fileName);
279                         }
280                         
281                         mHandler.post(new DialogDisplayer(
282                                 getString(R.string.fail_reason_failed_to_read_files,
283                                         builder.toString())));
284                     }
285                 }
286             }
287         }
288
289         private boolean doActuallyReadOneVCard(String canonicalPath, Account account,
290                 String charset, boolean showEntryParseProgress,
291                 VCardSourceDetector detector, List<String> errorFileNameList) {
292             final Context context = ImportVCardActivity.this;
293             VCardDataBuilder builder;
294             final String currentLanguage = Locale.getDefault().getLanguage();
295             int vcardType = VCardConfig.getVCardTypeFromString(
296                     context.getString(R.string.config_import_vcard_type));
297             if (charset != null) {
298                 builder = new VCardDataBuilder(charset, charset, false, vcardType, mAccount);
299             } else {
300                 charset = VCardConfig.DEFAULT_CHARSET;
301                 builder = new VCardDataBuilder(null, null, false, vcardType, mAccount);
302             }
303             builder.addEntryHandler(new EntryCommitter(mResolver));
304             if (showEntryParseProgress) {
305                 builder.addEntryHandler(new ProgressShower(mProgressDialogForReadVCard,
306                         context.getString(R.string.reading_vcard_message),
307                         ImportVCardActivity.this,
308                         mHandler));
309             }
310
311             try {
312                 if (!readOneVCardFile(canonicalPath, charset, builder, detector, false, null)) {
313                     return false;
314                 }
315             } catch (VCardNestedException e) {
316                 Log.e(LOG_TAG, "Never reach here.");
317             }
318             return true;
319         }
320
321         private boolean readOneVCardFile(String canonicalPath, String charset,
322                 VCardBuilder builder, VCardSourceDetector detector,
323                 boolean throwNestedException, List<String> errorFileNameList)
324                 throws VCardNestedException {
325             FileInputStream is;
326             try {
327                 is = new FileInputStream(canonicalPath);
328                 mVCardParser = new VCardParser_V21(detector);
329
330                 try {
331                     mVCardParser.parse(is, charset, builder, mCanceled);
332                 } catch (VCardVersionException e1) {
333                     try {
334                         is.close();
335                     } catch (IOException e) {
336                     }
337                     is = new FileInputStream(canonicalPath);
338
339                     try {
340                         mVCardParser = new VCardParser_V30();
341                         mVCardParser.parse(is, charset, builder, mCanceled);
342                     } catch (VCardVersionException e2) {
343                         throw new VCardException("vCard with unspported version.");
344                     }
345                 } finally {
346                     if (is != null) {
347                         try {
348                             is.close();
349                         } catch (IOException e) {
350                         }
351                     }
352                 }
353             } catch (IOException e) {
354                 Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
355
356                 mProgressDialogForReadVCard.dismiss();
357
358                 if (errorFileNameList != null) {
359                     errorFileNameList.add(canonicalPath);
360                 } else {
361                     mHandler.post(new DialogDisplayer(
362                             getString(R.string.fail_reason_io_error) +
363                                     ": " + e.getLocalizedMessage()));
364                 }
365                 return false;
366             } catch (VCardNotSupportedException e) {
367                 if ((e instanceof VCardNestedException) && throwNestedException) {
368                     throw (VCardNestedException)e;
369                 }
370                 if (errorFileNameList != null) {
371                     errorFileNameList.add(canonicalPath);
372                 } else {
373                     mHandler.post(new DialogDisplayer(
374                             getString(R.string.fail_reason_vcard_not_supported_error) +
375                             " (" + e.getMessage() + ")"));
376                 }
377                 return false;
378             } catch (VCardException e) {
379                 if (errorFileNameList != null) {
380                     errorFileNameList.add(canonicalPath);
381                 } else {
382                     mHandler.post(new DialogDisplayer(
383                             getString(R.string.fail_reason_vcard_parse_error) +
384                             " (" + e.getMessage() + ")"));
385                 }
386                 return false;
387             }
388             return true;
389         }
390
391         public void cancel() {
392             mCanceled = true;
393             if (mVCardParser != null) {
394                 mVCardParser.cancel();
395             }
396         }
397
398         public void onCancel(DialogInterface dialog) {
399             cancel();
400         }
401     }
402
403     private class ImportTypeSelectedListener implements
404             DialogInterface.OnClickListener {
405         public static final int IMPORT_ONE = 0;
406         public static final int IMPORT_MULTIPLE = 1;
407         public static final int IMPORT_ALL = 2;
408         public static final int IMPORT_TYPE_SIZE = 3;
409         
410         private int mCurrentIndex;
411
412         public void onClick(DialogInterface dialog, int which) {
413             if (which == DialogInterface.BUTTON_POSITIVE) {
414                 switch (mCurrentIndex) {
415                 case IMPORT_ALL:
416                     importMultipleVCardFromSDCard(mAllVCardFileList);
417                     break;
418                 case IMPORT_MULTIPLE:
419                     showDialog(R.id.dialog_select_multiple_vcard);
420                     break;
421                 default:
422                     showDialog(R.id.dialog_select_one_vcard);
423                     break;
424                 }
425             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
426                 finish();
427             } else {
428                 mCurrentIndex = which;
429             }
430         }
431     }
432     
433     private class VCardSelectedListener implements
434             DialogInterface.OnClickListener, DialogInterface.OnMultiChoiceClickListener {
435         private int mCurrentIndex;
436         private Set<Integer> mSelectedIndexSet;
437
438         public VCardSelectedListener(boolean multipleSelect) {
439             mCurrentIndex = 0;
440             if (multipleSelect) {
441                 mSelectedIndexSet = new HashSet<Integer>();
442             }
443         }
444
445         public void onClick(DialogInterface dialog, int which) {
446             if (which == DialogInterface.BUTTON_POSITIVE) {
447                 if (mSelectedIndexSet != null) {
448                     List<VCardFile> selectedVCardFileList = new ArrayList<VCardFile>();
449                     int size = mAllVCardFileList.size();
450                     // We'd like to sort the files by its index, so we do not use Set iterator. 
451                     for (int i = 0; i < size; i++) {
452                         if (mSelectedIndexSet.contains(i)) {
453                             selectedVCardFileList.add(mAllVCardFileList.get(i));
454                         }
455                     }
456                     importMultipleVCardFromSDCard(selectedVCardFileList);
457                 } else {
458                     importOneVCardFromSDCard(mAllVCardFileList.get(mCurrentIndex).getCanonicalPath());
459                 }
460             } else if (which == DialogInterface.BUTTON_NEGATIVE) {
461                 finish();
462             } else {
463                 // Some file is selected.
464                 mCurrentIndex = which;
465                 if (mSelectedIndexSet != null) {
466                     if (mSelectedIndexSet.contains(which)) {
467                         mSelectedIndexSet.remove(which);
468                     } else {
469                         mSelectedIndexSet.add(which);
470                     }
471                 }
472             }
473         }
474
475         public void onClick(DialogInterface dialog, int which, boolean isChecked) {
476             if (mSelectedIndexSet == null || (mSelectedIndexSet.contains(which) == isChecked)) {
477                 Log.e(LOG_TAG, String.format("Inconsist state in index %d (%s)", which,
478                         mAllVCardFileList.get(which).getCanonicalPath()));
479             } else {
480                 onClick(dialog, which);
481             }
482         }
483     }
484
485     /**
486      * Thread scanning VCard from SDCard. After scanning, the dialog which lets a user select
487      * a vCard file is shown. After the choice, VCardReadThread starts running.
488      */
489     private class VCardScanThread extends Thread implements OnCancelListener, OnClickListener {
490         private boolean mCanceled;
491         private boolean mGotIOException;
492         private File mRootDirectory;
493
494         // To avoid recursive link.
495         private Set<String> mCheckedPaths;
496         private PowerManager.WakeLock mWakeLock;
497
498         private class CanceledException extends Exception {
499         }
500
501         public VCardScanThread(File sdcardDirectory) {
502             mCanceled = false;
503             mGotIOException = false;
504             mRootDirectory = sdcardDirectory;
505             mCheckedPaths = new HashSet<String>();
506             PowerManager powerManager = (PowerManager)ImportVCardActivity.this.getSystemService(
507                     Context.POWER_SERVICE);
508             mWakeLock = powerManager.newWakeLock(
509                     PowerManager.SCREEN_DIM_WAKE_LOCK |
510                     PowerManager.ON_AFTER_RELEASE, LOG_TAG);
511         }
512
513         @Override
514         public void run() {
515             mAllVCardFileList = new Vector<VCardFile>();
516             try {
517                 mWakeLock.acquire();
518                 getVCardFileRecursively(mRootDirectory);
519             } catch (CanceledException e) {
520                 mCanceled = true;
521             } catch (IOException e) {
522                 mGotIOException = true;
523             } finally {
524                 mWakeLock.release();
525             }
526
527             if (mCanceled) {
528                 mAllVCardFileList = null;
529             }
530
531             mProgressDialogForScanVCard.dismiss();
532             mProgressDialogForScanVCard = null;
533
534             if (mGotIOException) {
535                 mHandler.post(new DialogDisplayer(R.id.dialog_io_exception));
536             } else if (mCanceled) {
537                 finish();
538             } else {
539                 int size = mAllVCardFileList.size();
540                 final Context context = ImportVCardActivity.this;
541                 if (size == 0) {
542                     mHandler.post(new DialogDisplayer(R.id.dialog_vcard_not_found));
543                 } else {
544                     startVCardSelectAndImport();
545                 }
546             }
547         }
548
549         private void getVCardFileRecursively(File directory)
550                 throws CanceledException, IOException {
551             if (mCanceled) {
552                 throw new CanceledException();
553             }
554
555             for (File file : directory.listFiles()) {
556                 if (mCanceled) {
557                     throw new CanceledException();
558                 }
559                 String canonicalPath = file.getCanonicalPath();
560                 if (mCheckedPaths.contains(canonicalPath)) {
561                     continue;
562                 }
563
564                 mCheckedPaths.add(canonicalPath);
565
566                 if (file.isDirectory()) {
567                     getVCardFileRecursively(file);
568                 } else if (canonicalPath.toLowerCase().endsWith(".vcf") &&
569                         file.canRead()){
570                     String fileName = file.getName();
571                     VCardFile vcardFile = new VCardFile(
572                             fileName, canonicalPath, file.lastModified());
573                     mAllVCardFileList.add(vcardFile);
574                 }
575             }
576         }
577
578         public void onCancel(DialogInterface dialog) {
579             mCanceled = true;
580         }
581
582         public void onClick(DialogInterface dialog, int which) {
583             if (which == DialogInterface.BUTTON_NEGATIVE) {
584                 mCanceled = true;
585             }
586         }
587     }
588
589     private void startVCardSelectAndImport() {
590         int size = mAllVCardFileList.size();
591         if (getResources().getBoolean(R.bool.config_import_all_vcard_from_sdcard_automatically)) {
592             importMultipleVCardFromSDCard(mAllVCardFileList);
593         } else if (size == 1) {
594             importOneVCardFromSDCard(mAllVCardFileList.get(0).getCanonicalPath());
595         } else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
596             mHandler.post(new DialogDisplayer(R.id.dialog_select_import_type));
597         } else {
598             mHandler.post(new DialogDisplayer(R.id.dialog_select_one_vcard));
599         }
600     }
601     
602     private void importMultipleVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
603         mHandler.post(new Runnable() {
604             public void run() {
605                 mVCardReadThread = new VCardReadThread(selectedVCardFileList);
606                 showDialog(R.id.dialog_reading_vcard);
607             }
608         });
609     }
610
611     private void importOneVCardFromSDCard(final String canonicalPath) {
612         mHandler.post(new Runnable() {
613             public void run() {
614                 mVCardReadThread = new VCardReadThread(canonicalPath);
615                 showDialog(R.id.dialog_reading_vcard);
616             }
617         });
618     }
619
620     private Dialog getSelectImportTypeDialog() {
621         DialogInterface.OnClickListener listener =
622             new ImportTypeSelectedListener();
623         AlertDialog.Builder builder = new AlertDialog.Builder(this)
624             .setTitle(R.string.select_vcard_title)
625             .setPositiveButton(android.R.string.ok, listener)
626             .setOnCancelListener(mCancelListener)
627             .setNegativeButton(android.R.string.cancel, mCancelListener);
628
629         String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
630         items[ImportTypeSelectedListener.IMPORT_ONE] =
631             getString(R.string.import_one_vcard_string);
632         items[ImportTypeSelectedListener.IMPORT_MULTIPLE] =
633             getString(R.string.import_multiple_vcard_string);
634         items[ImportTypeSelectedListener.IMPORT_ALL] =
635             getString(R.string.import_all_vcard_string);
636         builder.setSingleChoiceItems(items, ImportTypeSelectedListener.IMPORT_ONE, listener);
637         return builder.create();
638     }
639
640     private Dialog getVCardFileSelectDialog(boolean multipleSelect) {
641         int size = mAllVCardFileList.size();
642         VCardSelectedListener listener = new VCardSelectedListener(multipleSelect);
643         AlertDialog.Builder builder =
644             new AlertDialog.Builder(this)
645                 .setTitle(R.string.select_vcard_title)
646                 .setPositiveButton(android.R.string.ok, listener)
647                 .setOnCancelListener(mCancelListener)
648                 .setNegativeButton(android.R.string.cancel, mCancelListener);
649
650         CharSequence[] items = new CharSequence[size];
651         DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
652         for (int i = 0; i < size; i++) {
653             VCardFile vcardFile = mAllVCardFileList.get(i);
654             SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
655             stringBuilder.append(vcardFile.getName());
656             stringBuilder.append('\n');
657             int indexToBeSpanned = stringBuilder.length();
658             // Smaller date text looks better, since each file name becomes easier to read.
659             // The value set to RelativeSizeSpan is arbitrary. You can change it to any other
660             // value (but the value bigger than 1.0f would not make nice appearance :)
661             stringBuilder.append(
662                         "(" + dateFormat.format(new Date(vcardFile.getLastModified())) + ")");
663             stringBuilder.setSpan(
664                     new RelativeSizeSpan(0.7f), indexToBeSpanned, stringBuilder.length(),
665                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
666             items[i] = stringBuilder;
667         }
668         if (multipleSelect) {
669             builder.setMultiChoiceItems(items, (boolean[])null, listener);
670         } else {
671             builder.setSingleChoiceItems(items, 0, listener);
672         }
673         return builder.create();
674     }
675
676     private Dialog getReadingVCardDialog() {
677         if (mProgressDialogForReadVCard == null) {
678             String title = getString(R.string.reading_vcard_title);
679             String message = getString(R.string.reading_vcard_message);
680             mProgressDialogForReadVCard = new ProgressDialog(this);
681             mProgressDialogForReadVCard.setTitle(title);
682             mProgressDialogForReadVCard.setMessage(message);
683             mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
684             mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);
685             mVCardReadThread.start();
686         }
687         return mProgressDialogForReadVCard;
688     }
689
690     @Override
691     protected void onCreate(Bundle bundle) {
692         super.onCreate(bundle);
693
694         Intent intent = getIntent();
695         if (intent != null) {
696             final String accountName = intent.getStringExtra("account_name");
697             final String accountType = intent.getStringExtra("account_type");
698             if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
699                 mAccount = new Account(accountName, accountType);
700             }
701         } else {
702             Log.e(LOG_TAG, "intent does not exist");
703         }
704
705         startImportVCardFromSdCard();
706     }
707
708     @Override
709     protected Dialog onCreateDialog(int resId) {
710         switch (resId) {
711             case R.id.dialog_searching_vcard: {
712                 if (mProgressDialogForScanVCard == null) {
713                     String title = getString(R.string.searching_vcard_title);
714                     String message = getString(R.string.searching_vcard_message);
715                     mProgressDialogForScanVCard =
716                         ProgressDialog.show(this, title, message, true, false);
717                     mProgressDialogForScanVCard.setOnCancelListener(mVCardScanThread);
718                     mVCardScanThread.start();
719                 }
720                 return mProgressDialogForScanVCard;
721             }
722             case R.id.dialog_sdcard_not_found: {
723                 AlertDialog.Builder builder = new AlertDialog.Builder(this)
724                     .setTitle(R.string.no_sdcard_title)
725                     .setIcon(android.R.drawable.ic_dialog_alert)
726                     .setMessage(R.string.no_sdcard_message)
727                     .setOnCancelListener(mCancelListener)
728                     .setPositiveButton(android.R.string.ok, mCancelListener);
729                 return builder.create();
730             }
731             case R.id.dialog_vcard_not_found: {
732                 String message = (getString(R.string.scanning_sdcard_failed_message,
733                         getString(R.string.fail_reason_no_vcard_file)));
734                 AlertDialog.Builder builder = new AlertDialog.Builder(this)
735                     .setTitle(R.string.scanning_sdcard_failed_title)
736                     .setMessage(message)
737                     .setOnCancelListener(mCancelListener)
738                     .setPositiveButton(android.R.string.ok, mCancelListener);
739                 return builder.create();
740             }
741             case R.id.dialog_select_import_type: {
742                 return getSelectImportTypeDialog();
743             }
744             case R.id.dialog_select_multiple_vcard: {
745                 return getVCardFileSelectDialog(true);
746             }
747             case R.id.dialog_select_one_vcard: {
748                 return getVCardFileSelectDialog(false);
749             }
750             case R.id.dialog_reading_vcard: {
751                 return getReadingVCardDialog();
752             }
753             case R.id.dialog_io_exception: {
754                 String message = (getString(R.string.scanning_sdcard_failed_message,
755                         getString(R.string.fail_reason_io_error)));
756                 AlertDialog.Builder builder = new AlertDialog.Builder(this)
757                     .setTitle(R.string.scanning_sdcard_failed_title)
758                     .setIcon(android.R.drawable.ic_dialog_alert)
759                     .setMessage(message)
760                     .setOnCancelListener(mCancelListener)
761                     .setPositiveButton(android.R.string.ok, mCancelListener);
762                 return builder.create();
763             }
764             case R.id.dialog_error_with_message: {
765                 String message = mErrorMessage;
766                 if (TextUtils.isEmpty(message)) {
767                     Log.e(LOG_TAG, "Error message is null while it must not.");
768                     message = getString(R.string.fail_reason_unknown);
769                 }
770                 AlertDialog.Builder builder = new AlertDialog.Builder(this)
771                     .setTitle(getString(R.string.reading_vcard_failed_title))
772                     .setIcon(android.R.drawable.ic_dialog_alert)
773                     .setMessage(message)
774                     .setOnCancelListener(mCancelListener)
775                     .setPositiveButton(android.R.string.ok, mCancelListener);
776                 return builder.create();
777             }
778         }
779
780         return super.onCreateDialog(resId);
781     }
782
783     @Override
784     protected void onStop() {
785         super.onStop();
786         if (mVCardReadThread != null) {
787             // The Activity is no longer visible. Stop the thread.
788             mVCardReadThread.cancel();
789             mVCardReadThread = null;
790         }
791
792         // ImportVCardActivity should not be persistent. In other words, if there's some
793         // event calling onStop(), this Activity should finish its work and give the main
794         // screen back to the caller Activity.
795         if (!isFinishing()) {
796             finish();
797         }
798     }
799
800     @Override
801     public void finalize() {
802         if (mVCardReadThread != null) {
803             // Not sure this procedure is really needed, but just in case...
804             Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
805             mVCardReadThread.cancel();
806             mVCardReadThread = null;
807         }
808     }
809
810     /* public methods */
811
812     /**
813      * Tries to start importing VCard. If there's no SDCard available,
814      * an error dialog is shown. If there is, start scanning using another thread
815      * and shows a progress dialog. Several interactions will occur.
816      * This method should be called from a thread with a looper (like Activity).
817      */
818     public void startImportVCardFromSdCard() {
819         File file = new File("/sdcard");
820         if (!file.exists() || !file.isDirectory() || !file.canRead()) {
821             showDialog(R.id.dialog_sdcard_not_found);
822         } else {
823             File sdcardDirectory = new File("/sdcard");
824             mVCardScanThread = new VCardScanThread(sdcardDirectory);
825             showDialog(R.id.dialog_searching_vcard);
826         }
827     }
828 }