You are on page 1of 18

/* * * * * * * * * 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 import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import import android.accounts.Account; android.app.Activity; android.app.AlertDialog; android.app.Dialog; android.app.ProgressDialog; android.content.ContentResolver; android.content.ContentUris; android.content.Context; android.content.DialogInterface; android.content.Intent; android.content.DialogInterface.OnCancelListener; android.content.DialogInterface.OnClickListener; android.net.Uri; android.os.Bundle; android.os.Environment; android.os.Handler; android.os.PowerManager; android.pim.vcard.VCardConfig; android.pim.vcard.VCardEntryCommitter; android.pim.vcard.VCardEntryConstructor; android.pim.vcard.VCardEntryCounter; android.pim.vcard.VCardInterpreter; android.pim.vcard.VCardInterpreterCollection; android.pim.vcard.VCardParser_V21; android.pim.vcard.VCardParser_V30; android.pim.vcard.VCardSourceDetector; android.pim.vcard.exception.VCardException; android.pim.vcard.exception.VCardNestedException; android.pim.vcard.exception.VCardNotSupportedException; android.pim.vcard.exception.VCardVersionException; android.provider.ContactsContract.RawContacts; android.text.SpannableStringBuilder; android.text.Spanned; android.text.TextUtils; android.text.style.RelativeSizeSpan; android.util.Log;

import com.android.contacts.model.Sources; import com.android.contacts.util.AccountSelectionUtil; import java.io.File; import java.io.IOException;

Set.DateFormat. java.. java. java. VCardReadThread mVCardReadThread. VCardScanThread mVCardScanThread. mLastModified = lastModified. Must not be null except after onDestroy().util.getName() { return mName.Vector.AccountSelectedListener mAccountSelectionListen er.util. java. java.getCanonicalPath() { return mCanonicalPath. private AccountSelectionUtil. public More .text. private Account mAccount.text. private String mErrorMessage.. } public long More . private Handler mHandler = new Handler()..ImportVCardActivity extends Activity { private static final String LOG_TAG = "ImportVCardActivity". java.List. java.. private private private private List<VCardFile> mAllVCardFileList.util.Arrays. long lastModifi ed) { mName = name. private boolean mNeedReview = false.HashSet.getLastModified() { return mLastModified. private static final boolean DO_PERFORMANCE_PROFILE = false.ArrayList. java. } public String More . private String mCanonicalPath... class More .InputStream..io.VCardFile { private String mName. } public String More . mCanonicalPath = canonicalPath.import import import import import import import import import import import java. private long mLastModified.Date.Locale. java..VCardFile(String name. String canonicalPath... java..util.util. .util. // Run on the UI thread. ProgressDialog mProgressDialogForReadVCard.util.SimpleDateFormat. private ProgressDialog mProgressDialogForScanVCard. } } public class More ..util.

VCardReadThread(final List<VCardFile> selectedVCardFileLi st) { mSelectedVCardFileList = selectedVCardFileList. private class More .OnCancelList ener { public void More .. init().DialogDisplayer implements Runnable { private final int mResId......run() { showDialog(mResId).// Runs on the UI thread. private Uri mUri.id. int which) { finish(). } private void More .CancelListener implements DialogInterface. private List<VCardFile> mSelectedVCardFileList. private List<String> mErrorFileNameList. private PowerManager.OnClickListener.. } public void More .getContentResolver(). mErrorMessage = errorMessage.this.. private class More . private VCardParser_V21 mVCardParser. } public void More . public More .OnCancelListener { private ContentResolver mResolver..SCREEN_DIM_WAKE_LOCK | . } public More . } } private CancelListener mCancelListener = new CancelListener().init() { Context context = ImportVCardActivity.newWakeLock( PowerManager. } } private class More . DialogInterface.dialog_error_with_message.VCardReadThread extends Thread implements DialogInterface.onCancel(DialogInterface dialog) { finish().. private File mTempFile.POWER_SERVICE).. private boolean mCanceled. mWakeLock = powerManager. mErrorFileNameList = new ArrayList<String>().... public More ..... mResolver = context....DialogDisplayer(String errorMessage) { mResId = R.DialogDisplayer(int resId) { mResId = resId.WakeLock mWakeLock. PowerManager powerManager = (PowerManager)context.onClick(DialogInterface dialog. init(). } public More .getSystemService( Context.VCardReadThread(Uri uri) { mUri = uri..

. if (DO_PERFORMANCE_PROFILE) { start = System. // Count the number of VCard entries mProgressDialogForReadVCard.. OutOfMemoryError). LOG_TAG). detector)).ON_AFTER_RELEASE.finalize() { if (mWakeLock != null && mWakeLock. null. // Some malicious vCard data may make this thread broken // (e.PowerManager. } catch (VCardNestedException e2) { result = false. // Try again with the detector. builderCollection. } } @Override public void More . null). VCardInterpreterCollection builderCollection = new VCardInte rpreterCollection( Arrays. } } if (DO_PERFORMANCE_PROFILE) { long time = System. "time for counting the number of vCard en tries: " + time + " ms"). mTempFile = null. some should be done.d(LOG_TAG.setProgress(0). null). try { if (mUri != null) { // Read one vCard expressed by mUri final Uri targetUri = mUri. // Even in such cases. " + e2). VCardConfig. "Must not reach here. } catch (VCardNestedException e) { try { // Assume that VCardSourceDetector was able to detec t the source.release(). long start. Log. mProgressDialogForReadVCard. boolean result. detect or. VCardSourceDetector detector = new VCardSourceDetector().acquire(). } VCardEntryCounter counter = new VCardEntryCounter()..asList(counter.DEFAULT_CHARSET. } @Override public void More .currentTimeMillis().setIndeterminate(true). result = readOneVCardFile(targetUri.run() { boolean shouldCallFinish = true.DEFAULT_CHARSET. counter. try { result = readOneVCardFile(targetUri. Log. mWakeLock. mProgressDialogForReadVCard.setProgressNumberFormat("").isHeld()) { mWakeLock.e(LOG_TAG. false. Uri createdUri = null. } .start. true. VCardConfig.g.currentTimeMillis() ..

null.setMax(mSelectedVCardFileList. } } catch (VCardNestedException e) { // Assume that VCardSourceDetector was able to detec t the source.setProgressNumberFormat( getString(R. mProgressDialogForReadVCard.incrementProgressBy(1). mProgressDialogForReadVCard. mProgressDialogForReadVCard. true. } } } finally { mWakeLock. if (mTempFile != null) { if (!mTempFile. for (VCardFile vcardFile : mSelectedVCardFileList) { if (mCanceled) { return. mProgressDialogForReadVCard. } else { // Read multiple files.setMax(counter. return. String charset = detector.isEmpty ()) { . mErrorFileNameList).dismiss().if (!result) { shouldCallFinish = false.DEFAULT _CHARSET. charset . VCardConfig. } // finish() is called via mCancelListener. } // TODO: detect scheme! final Uri targetUri = Uri."). if (shouldCallFinish && !isFinishing()) { if (mErrorFileNameList == null || mErrorFileNameList.string.setProgress(0). try { if (!readOneVCardFile(targetUri.reading_vcard_contacts)). detector.release(). mErrorFileNameList)) { continue. VCardSourceDetector detector = new VCardSourceDetector() .setIndeterminate(false). } String charset = detector. mAccount. mProgressDialogForReadVCard. } mTempFile = null. true. mErrorFileNameList). mProgressDialogForReadVCard. detector. createdUri = doActuallyReadOneVCard(targetUri.string. detector.getCanonicalPath()). doActuallyReadOneVCard(targetUri. null.getCount()).setProgressNumberFormat( getString(R. charset.parse("file://" + vcardFile.delete()) { Log. which is used in Dial ogDisplayer.reading_vcard_files)).si ze()).getEstimatedCharset(). false. "Failed to delete a cache file. } mProgressDialogForReadVCard.w(LOG_TAG.getEstimatedCharset(). mProgressDialogForReadVCard.

rawCont actId)).toString()))). boolean showEntryParseProgress. ").append(". Account account. if (charset != null) { builder = new VCardEntryConstructor(charset.string. } else { charset = VCardConfig. } else { builder. VCardEntryConstructor builder. null. charset. if (createdUri != null) { // get contact_id of this raw_contact final long rawContactId = ContentUris.getLanguage().finish(). Uri contactUri = RawContacts.getDefault().getVCardTypeFromString( context. Intent viewIntent = new Intent(Intent.fail_reason_failed_to_read_fi les. final String currentLanguage = Locale. } } } } private Uri More . "Prepare to review the imported contact"). vca rdType.DEFAULT_CHARSET. .append(fileName).config_import_vcard_type)).. startActivity(viewIntent).CONTENT_URI.. } runOnUIThread(new DialogDisplayer( getString(R.getContactLookupUri ( getContentResolver(). if (mNeedReview) { mNeedReview = false.ACTION_VIE W. } builder. int vcardType = VCardConfig. false. ContentUris.withAp pendedId( RawContacts. boolean first = true. builder = new VCardEntryConstructor(null. for (String fileName : mErrorFileNameList) { if (first) { first = false. mAccount). builder.v("importVCardActivity". } } } else { StringBuilder builder = new StringBuilder(). List<String> errorFileNameList) { final Context context = ImportVCardActivity.string.this. } VCardEntryCommitter committer = new VCardEntryCommitter(mResolver). contactUri). String charset.doActuallyReadOneVCard(Uri uri. Log. mAccount). false. vcardType .parseId(cr eatedUri). VCardSourceDetector detector.getString(R.

.close(). if (showEntryParseProgress) { builder.addEntryHandler(new ProgressShower(mProgressDialogForRea dVCard. charset. builder. try { mVCardParser. mCanceled). nu ll)) { return null. } catch (VCardVersionException e1) { try { is.get(0).getCreatedUris(). try { is = mResolver. } catch (IOException e) { } } } } catch (IOException e) { .this. mHandler)). try { mVCardParser = new VCardParser_V30().parse(is. ImportVCardActivity. } } catch (VCardNestedException e) { Log.openInputStream(uri).parse(is.builder.openInputStream(uri).readOneVCardFile(Uri uri. } } finally { if (is != null) { try { is. builder. List<String> errorFileNameList) throws VCardNestedException { InputStream is. "). VCardInterpreter builder. charset. } private boolean More .string.close(). context. false."). boolean throwNestedException. return (createdUris == null || createdUris.addEntryHandler(committer). mVCardParser..clear(). } is = mResolver.e(LOG_TAG. builder. } final ArrayList<Uri> createdUris = committer.reading_vcard_message). String charset. } try { if (!readOneVCardFile(uri. ((VCardEntryConstructor)builder). "Never reach here. detector. charset. VCardSourceDetector detector. mVCardParser = new VCardParser_V21(detector). mCanceled).getString(R. } catch (IOException e) { } if (builder instanceof VCardEntryConstructor) { // Let the object clean up internal temporal objects. } catch (VCardVersionException e2) { throw new VCardException("vCard with unspported version.size() != 1) ? null : cre atedUris.

private int mCurrentIndex.toString()). } return true. } else { runOnUIThread(new DialogDisplayer( getString(R. } public void More ...ImportTypeSelectedListener implements DialogInterface.getMessage() + ")")).fail_reason_vcard_not_supported_e rror) + " (" + e. "IOException was emitted: " + e. public static final int IMPORT_MULTIPLE = 1.dismiss().e(LOG_TAG. } } public void More . } return false.Log. } } private class More .add(uri. if (errorFileNameList != null) { errorFileNameList.string.toString())..string. } catch (VCardException e) { if (errorFileNameList != null) { errorFileNameList.getMessage() + ")")). public static final int IMPORT_TYPE_SIZE = 3..getLocalizedMessage())).add(uri.fail_reason_io_error) + ": " + e. . } if (errorFileNameList != null) { errorFileNameList.onCancel(DialogInterface dialog) { cancel()..OnClickListener { public static final int IMPORT_ONE = 0.add(uri. } return false.string. } else { runOnUIThread(new DialogDisplayer( getString(R. public static final int IMPORT_ALL = 2. } else { runOnUIThread(new DialogDisplayer( getString(R. } catch (VCardNotSupportedException e) { if ((e instanceof VCardNestedException) && throwNestedException) { throw (VCardNestedException)e.cancel().cancel() { mCanceled = true. if (mVCardParser != null) { mVCardParser. mProgressDialogForReadVCard.toString()). } return false..fail_reason_vcard_parse_error) + " (" + e.getMessage()).

} else { String canonicalPath = mAllVCardFileList.onClick(DialogInterface dialog. so we do not us e Set iterator.size().VCardSelectedListener(boolean multipleSelect) { mCurrentIndex = 0..VCardSelectedListener implements DialogInterface. int which) { if (which == DialogInterface. DialogInterface.. mCurrentIndex = which. break.add(mAllVCardFileList. i < size.BUTTON_NEGATIVE) { finish(). if (mSelectedIndexSet != null) { . // We'd like to sort the files by its index. private Set<Integer> mSelectedIndexSet. } } importMultipleVCardFromSDCard(selectedVCardFileList). case IMPORT_MULTIPLE: showDialog(R. break.public void More .contains(i)) { selectedVCardFileList.get(i)).onClick(DialogInterface dialog.. } } } private class More . for (int i = 0.OnClickListener. int size = mAllVCardFileList.BUTTON_POSITIVE) { if (mSelectedIndexSet != null) { List<VCardFile> selectedVCardFileList = new ArrayList<VCardF ile>().. i++) { if (mSelectedIndexSet. } } else if (which == DialogInterface.dialog_select_multiple_vcard). getCanonicalPath(). final Uri uri = Uri. } else { mCurrentIndex = which.BUTTON_NEGATIVE) { finish(). } } public void More . public More ..BUTTON_POSITIVE) { switch (mCurrentIndex) { case IMPORT_ALL: importMultipleVCardFromSDCard(mAllVCardFileList). if (multipleSelect) { mSelectedIndexSet = new HashSet<Integer>().id. } else { // Some file is selected. break.parse("file://" + canonicalPath). importOneVCardFromSDCard(uri). } } else if (which == DialogInterface.OnMultiChoiceClickL istener { private int mCurrentIndex.get(mCurrentIndex)..dialog_select_one_vcard). int which) { if (which == DialogInterface. default: showDialog(R...id.

getVCardFileRecursively(mRootDirectory). mCheckedPaths = new HashSet<String>(). mWakeLock = powerManager. private File mRootDirectory. } catch (CanceledException e) { mCanceled = true. PowerManager powerManager = (PowerManager)ImportVCardActivity.newWakeLock( PowerManager.WakeLock mWakeLock.. } } } } public void More .run() { mAllVCardFileList = new Vector<VCardFile>().get(which). OnClickListener { private boolean mCanceled.. try { mWakeLock.contains(which) == isChecked)) { Log.contains(which)) { mSelectedIndexSet. // To avoid recursive link. private PowerManager..CanceledException extends Exception { } public More .getCanonicalPath())).add(which)..POWER_SERVICE).VCardScanThread extends Thread implements OnCancelListener . private Set<String> mCheckedPaths.e(LOG_TAG. LOG_TAG). } catch (IOException e) { mGotIOException = true. private boolean mGotIOException. private class More . mGotIOException = false.this. mRootDirectory = sdcardDirectory. .. } else { onClick(dialog. } } } private class More .. } finally { mWakeLock.remove(which). mAllVCardFileList. boolean i sChecked) { if (mSelectedIndexSet == null || (mSelectedIndexSet..VCardScanThread(File sdcardDirectory) { mCanceled = false.g etSystemService( Context. } else { mSelectedIndexSet. String.. int which..format("Inconsist state in index %d (%s)". which).onClick(DialogInterface dialog. } @Override public void More .if (mSelectedIndexSet.ON_AFTER_RELEASE.SCREEN_DIM_WAKE_LOCK | PowerManager. which..release().acquire().

if (mCheckedPaths. } else if (canonicalPath.id.canRead()){ String fileName = file. } mProgressDialogForScanVCard. } // e. file. mAllVCardFileList.listFiles(). secured directory may return null toward listFiles(). "listFiles() returned null (directory: " + direct ory + ")").add(canonicalPath).lastModified()). canonicalPath. IOException { if (mCanceled) { throw new CanceledException().listFiles()) { if (mCanceled) { throw new CanceledException(). } for (File file : directory.id. } else { int size = mAllVCardFileList. mProgressDialogForScanVCard = null.getCanonicalPath(). } mCheckedPaths.dismiss(). VCardFile vcardFile = new VCardFile( fileName.endsWith(". } else { startVCardSelectAndImport(). } String canonicalPath = file.g.add(vcardFile). } else if (mCanceled) { finish(). return. } } } private void More .dialog_io_exception)).getName(). if (files == null) { Log.toLowerCase().dialog_vcard_not_foun d)). } } } .contains(canonicalPath)) { continue.isDirectory()) { getVCardFileRecursively(file).. if (size == 0) { runOnUIThread(new DialogDisplayer(R.this.vcf") && file. if (file..getVCardFileRecursively(File directory) throws CanceledException.size(). if (mGotIOException) { runOnUIThread(new DialogDisplayer(R.w(LOG_TAG. final Context context = ImportVCardActivity.} if (mCanceled) { mAllVCardFileList = null. final File[] files = directory.

getCanonicalPath(). listener) .R.getBoolean(R. showDialog(R.dialog_reading_vcard)..dialog_reading_vcard).setPositiveButton(android.bool.onCancel(DialogInterface dialog) { mCanceled = true.id. } private void More .ok. if (getResources().config_allow_users_select_al l_vcard_import)) { runOnUIThread(new DialogDisplayer(R. } public void More .id.string. } private Dialog More .importMultipleVCardFromSDCard(final List<VCardFile> sel ectedVCardFileList) { runOnUIThread(new Runnable() { public void More .setNegativeButton(android...size().config_import_all_vcard_from_sdcard _automatically)) { importMultipleVCardFromSDCard(mAllVCardFileList). } }).cancel. showDialog(R. String[] items = new String[ImportTypeSelectedListener.setTitle(R.Builder builder = new AlertDialog.bool. int which) { if (which == DialogInterface.onClick(DialogInterface dialog.startVCardSelectAndImport() { int size = mAllVCardFileList.IMPORT_TYPE_SIZE] .getBoolean(R. importOneVCardFromSDCard(uri).R. } else if (getResources()...dialog_select_one_vcard)).parse("file://" + canonicalPath).get(0)..string. items[ImportTypeSelectedListener.Builder(this) .run() { mVCardReadThread = new VCardReadThread(uri).IMPORT_ONE] = .. } else { runOnUIThread(new DialogDisplayer(R. Uri uri = Uri.OnClickListener listener = new ImportTypeSelectedListener().id. } })....string.run() { mVCardReadThread = new VCardReadThread(selectedVCardFileList)..select_vcard_title) .public void More .BUTTON_NEGATIVE) { mCanceled = true. mCancelListener).setOnCancelListener(mCancelListener) . } else if (size == 1) { String canonicalPath = mAllVCardFileList. } } private void More .dialog_select_import_type)). } } } private void More . AlertDialog...id....getSelectImportTypeDialog() { DialogInterface.importOneVCardFromSDCard(final Uri uri) { runOnUIThread(new Runnable() { public void More .

. . length(). Spanned. 0. final String accountType = intent. stringBuilder.getStringExtra("account_name"). CharSequence[] items = new CharSequence[size]. } if (multipleSelect) { builder. listener).create(). stringBuilder. final Intent intent = getIntent().IMPORT_MULTIPLE] = getString(R. } else { builder.string. since each file name becomes easi er to read.setSpan( new RelativeSizeSpan(0.setSingleChoiceItems(items. stringBuilder.setSingleChoiceItems(items. builder. listener).cancel.onCreate(bundle). } @Override protected void More . stringBuilder.ok.Builder(this) .0f would not make nice appeara nce :) stringBuilder.R. i < size.IMPORT_ON E. } return builder.string. listener) .setOnCancelListener(mCancelListener) . You can change it to any other // value (but the value bigger than 1. AlertDialog. DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").getVCardFileSelectDialog(boolean multipleSelect) { int size = mAllVCardFileList.length(). for (int i = 0.. mCancelListener). indexToBeSpanned..R. return builder. } private Dialog More .setTitle(R.import_multiple_vcard_string). items[ImportTypeSelectedListener. items[ImportTypeSelectedListener.IMPORT_ALL] = getString(R. if (intent != null) { final String accountName = intent.size().getLastModifi ed())) + ")").import_one_vcard_string).getStringExtra("account_type").string. (boolean[])null. VCardSelectedListener listener = new VCardSelectedListener(multipleSelec t).string.getString(R. int indexToBeSpanned = stringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE).format(new Date(vcardFile. // The value set to RelativeSizeSpan is arbitrary.7f). i++) { VCardFile vcardFile = mAllVCardFileList.get(i).setNegativeButton(android. SpannableStringBuilder stringBuilder = new SpannableStringBuilder(). items[i] = stringBuilder.select_vcard_title) . ImportTypeSelectedListener.append(vcardFile.Builder builder = new AlertDialog. // Smaller date text looks better.setMultiChoiceItems(items..append('\n').onCreate(Bundle bundle) { super.getName()).append( "(" + dateFormat.string. listener).string.setPositiveButton(android.import_all_vcard_string).create().

mAccountSelectionListener = new AccountSelectionUtil.more than one accounts -> ask the user // .size(). path = " + uri). final List<Account> accountList = sources.startImport() { Intent intent = getIntent(). startImport().isEmpty(accountName) && !TextUtils. resId) { @Override public void More . accountType).. // Instead of using Intent mechanism. showDialog(resId).AccountSelectedListener( this. final String action = intent. final int size = accountList.isEmpty(accountTyp e)) { mAccount = new Account(accountName. } if (uri != null) { importOneVCardFromSDCard(uri).onClick(DialogInterface dialog.getAction().. // to avoid throwing an Intent to itself again. call the relevant private method.v(LOG_TAG. final Uri uri = intent.equals(action)) { // Import the file directly and then go to EDIT screen mNeedReview = true. so we show the UI instead. } } else { Log. } }.. if (Intent. Log.just one account -> use the account without asking the user // .get(0) : null.string.ACTION_VIEW.getAccounts(true). if (mAccount == null) { // There's three possibilities: // . } else { doScanExternalStorageAndImportVCard(). int whic h) { dialog.e(LOG_TAG.if (!TextUtils.getData(). mAccount = mAccountList.import_from_sdcard. } } startImport(). return.no account -> use phone-local storage without asking the user final Sources sources = Sources. if (size > 1) { final int resId = R.dismiss(). } else { mAccount = size > 0 ? accountList..getInstance(this). "intent does not exist"). } // The caller often does not know account information at all. "action = " + action + " .get(which). } private void More . accountList. } } .

dialog_reading_vcard: { if (mProgressDialogForReadVCard == null) { String title = getString(R. true.no_sdcard_title) .Builder builder = new AlertDialog.id.setMessage(R.setTitle(R.scanning_sdcard_failed_title) .show(this.string.id.setOnCancelListener(mVCardScanTh read).string.string.no_sdcard_message) . } return AccountSelectionUtil. } case R. mProgressDialogForReadVCard = new ProgressDialog(this).R. message. return builder.reading_vcard_title). } case R. new CancelListener()). .setOnCancelListener(mCancelListener) .R. false).setPositiveButton(android. title. } return mProgressDialogForScanVCard."). mAccountSelectionListener. resId.string.string.dialog_select_multiple_vcard: { return getVCardFileSelectDialog(true).Builder(this) . AlertDialog.searching_vcard_message) .scanning_sdcard_failed_mess age.string.ok.Builder(this) ..ic_dialog_alert) .start(). } case R.getSelectAccountDialog(this.setIcon(android. String message = getString(R. mVCardScanThread.Builder builder = new AlertDialog.dialog_select_import_type: { return getSelectImportTypeDialog()..dialog_select_one_vcard: { return getVCardFileSelectDialog(false).string.id.setMessage(message) .create().ok.string.string. } case R. String message = getString(R.searching_vcard_title).string. mProgressDialogForScanVCard = ProgressDialog.id.setOnCancelListener(mCancelListener) .onCreateDialog(int resId) { switch (resId) { case R. } case R. } case R.@Override protected Dialog More . mCancelListener).drawable.id.id.reading_vcard_message).dialog_searching_vcard: { if (mProgressDialogForScanVCard == null) { String title = getString(R.fail_reason_no_vcard_file))).dialog_sdcard_not_found: { AlertDialog.dialog_vcard_not_found: { String message = (getString(R. mCancelListener).string.R.import_from_sdcard: { if (mAccountSelectionListener == null) { throw new NullPointerException( "mAccountSelectionListener must not be null.id. getString(R.create().setTitle(R.string. mProgressDialogForScanVCard.setPositiveButton(android. return builder. } case R.

scanning_sdcard_failed_title) .cancel(). message = getString(R. In other words.setMessage(message). if (mVCardReadThread != null) { // The Activity is no longer visible.ic_dialog_alert) .setTitle(title).onPause(). mCancelListener). return builder.setMessage(message) .drawable. } case R. return builder. getString(R. mVCardReadThread.").Builder(this) .Builder builder = new AlertDialog. } } . mProgressDialogForReadVCard.Builder builder = new AlertDialog.mProgressDialogForReadVCard.ok.setProgressStyle(ProgressDialog.reading_vcard_failed_title)) . } @Override protected void More .onPause() { super.setIcon(android..setIcon(android.setOnCancelListener(mCancelListener) .setPositiveButton(android.string.id.fail_reason_unknown).e(LOG_TAG. "Error message is null while it must not.R.id. this Activity should finish its work and giv e the main // screen back to the caller Activity.setTitle(getString(R.string. mVCardReadThread = null.setOnCancelListener(mVCardReadTh read).setMessage(message) . if (TextUtils. } // ImportVCardActivity should not be persistent. mCancelListener). AlertDialog..drawable. } case R. Stop the thread.setTitle(R.R.string. mVCardReadThread.setOnCancelListener(mCancelListener) .scanning_sdcard_failed_mess age.create().isEmpty(message)) { Log.string. } AlertDialog.fail_reason_io_error))). } return mProgressDialogForReadVCard.ic_dialog_alert) .string.setPositiveButton(android.string.start(). mProgressDialogForReadVCard.dialog_io_exception: { String message = (getString(R. mProgressDialogForReadVCard.string. if (!isFinishing()) { finish(). if ther e's some // event calling onPause(). } } return super. STYLE_HORIZONTAL).onCreateDialog(resId).create().R.ok.Builder(this) .dialog_error_with_message: { String message = mErrorMessage.R.

. super.isAlive() && attempts < 10) { try { Thread. } else { mHandler.cancel(). but just in case. // clearing the message queue is not enough.@Override protected void More . while (mVCardReadThread. } // Callbacks messages have what == 0. mVCardReadThread. mVCardReadThread = null. } catch (InterruptedException ie) { // Keep on going until max attempts is reached.."). "VCardReadThread exists while this Activity is now be ing killed!")..finalize() { // TODO: This should not be needed. No dialog is shown. Log. "VCardReadThread exists while this Activity is now be ing killed!").. int attempts = 0. if (mHandler..w(LOG_TAG. Log. // Need to make sure any worker thread is done before we flush and // nullify the message handler.cancel(). } mHandler = null.sleep(20). if (mVCardReadThread != null) { // Not sure this procedure is really needed.w(LOG_TAG.. Throw exception instead. } } @Override public void More .currentThread(). "Handler object is null.onDestroy().").isAlive()) { // Find out why the thread did not exit in a timely // fashion.e(LOG_TAG. one would have to // make sure that the handler does not run any callback when // this activity isFinishing(). } .. } private void More . "VCardReadThread is still alive after max attempt s. Last resort: increase the sleep duration // and/or the number of attempts. } if (mVCardReadThread.onDestroy() { // The code assumes the handler runs on the UI thread.hasMessages(0)) { mHandler. } attempts++.runOnUIThread(Runnable runnable) { if (mHandler == null) { Log.. If not.post(runnable). // Prevents memory leaks by breaking any circular depe ndency. } mVCardReadThread = null.removeMessages(0). if (mVCardReadThread != null) { Log.e(LOG_TAG. mVCardReadThread.

if (!file.exists() || !file.getExternalStorageDirectory().. final File file = Environment.id.isDirectory() || !file.dialog_sdcard_not_found).id..dialog_searching_vcard). } } } .canRead()) { showDialog(R. } else { mVCardScanThread = new VCardScanThread(file). showDialog(R.} private void More .doScanExternalStorageAndImportVCard() { // TODO: should use getExternalStorageState().