Refactor vCard importer so that it uses Service during the actual import.
This is for makeing the importer torelate various troubles around Activity using Thread.
Screen orientation breaks the Activity while the import is on its way, and the
regenerated Activity cannot see the status of the previous import.
With using Service, now we have to let vCard importer re-entrant toward multiple import request.
I confirmed the newly added code works fine, but we may have bugs around them.
One significant bug is that (again) we cannot import a big vCard via Uri whose permission is
temporarily guaranteed by its provider (e.g. Email's AttachmentProvider).
Bug: 2523624
Bug: 2158329
Bug: 2572275
Change-Id: I961aecc0848176bdbcab6527a25f60352d957ee0
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a7eec1d..d920701 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -463,6 +463,10 @@
</intent-filter>
</activity>
+ <service
+ android:name=".ImportVCardService"
+ android:exported="false" />
+
<activity android:name=".ExportVCardActivity"
android:theme="@style/BackgroundOnly" />
</application>
diff --git a/res/layout/status_bar_ongoing_event_progress_bar.xml b/res/layout/status_bar_ongoing_event_progress_bar.xml
new file mode 100644
index 0000000..d1bce1a
--- /dev/null
+++ b/res/layout/status_bar_ongoing_event_progress_bar.xml
@@ -0,0 +1,111 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2010, 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.
+ /
+TODO: This is copied from DownloadProvider, and looks similar to Bluetooth's.
+ It might be better to have this in framework.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:drawable/status_bar_item_app_background"
+ >
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ >
+
+ <LinearLayout
+ android:layout_width="40dp"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingTop="8dp"
+ android:focusable="true"
+ android:clickable="true"
+ >
+ <com.android.server.status.AnimatedImageView
+ android:id="@+id/appIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:src="@android:drawable/sym_def_app_icon"
+ />
+ <TextView android:id="@+id/progress_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ff000000"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:layout_gravity="center_horizontal"
+ />
+ </LinearLayout>
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:focusable="true"
+ android:clickable="true"
+ >
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:focusable="true"
+ android:clickable="true"
+ android:layout_alignParentTop="true"
+ android:paddingTop="10dp"
+ >
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textSize="18sp"
+ android:textColor="#ff000000"
+ android:paddingLeft="2dp"
+ />
+ <TextView android:id="@+id/description"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="#ff000000"
+ android:singleLine="true"
+ android:textSize="14sp"
+ android:paddingLeft="5dp"
+ />
+ </LinearLayout>
+ <ProgressBar android:id="@+id/progress_bar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:paddingBottom="8dp"
+ android:paddingRight="25dp"
+ />
+ </RelativeLayout>
+ </LinearLayout>
+
+ <com.android.server.status.AnimatedImageView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@android:drawable/divider_horizontal_bright"
+ />
+
+</LinearLayout>
+
diff --git a/res/values/ids.xml b/res/values/ids.xml
index ceb10f8..692c413 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -37,7 +37,7 @@
<item type="id" name="dialog_multiple_contact_delete_confirmation"/>
<item type="id" name="dialog_readonly_contact_delete_confirmation"/>
- <!-- For ExportVCard -->
+ <!-- For ExportVCardActivity -->
<item type="id" name="dialog_export_confirmation"/>
<item type="id" name="dialog_exporting_vcard"/>
<item type="id" name="dialog_fail_to_export_with_reason"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index abdf496..168880d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -658,7 +658,7 @@
<!-- 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 -->
+ <!-- Dialog title shown when searching vCard data from SD Card -->
<string name="searching_vcard_title">Searching for vCard</string>
<!-- Action string for selecting SIM for importing contacts -->
@@ -688,13 +688,13 @@
than one vCard files available in the system. -->
<string name="import_all_vcard_string">Import all vCard files</string>
- <!-- Dialog message shown when searching VCard data from SD Card -->
+ <!-- Dialog message shown when searching vCard data from SD Card -->
<string name="searching_vcard_message">Searching for vCard data on SD card</string>
- <!-- Dialog title shown when scanning VCard data failed. -->
+ <!-- Dialog title shown when scanning vCard data failed. -->
<string name="scanning_sdcard_failed_title">Scanning SD card failed</string>
- <!-- Dialog message shown when searching VCard data failed.
+ <!-- Dialog message shown when searching vCard data failed.
An exact reason for the failure should -->
<string name="scanning_sdcard_failed_message">Scanning SD card failed (Reason: \"<xliff:g id="fail_reason">%s</xliff:g>\")</string>
@@ -724,28 +724,35 @@
<!-- The failed reason which should not be shown but it may in some buggy condition. -->
<string name="fail_reason_unknown">Unknown error</string>
- <!-- Dialog title shown when a user is asked to select VCard file -->
+ <!-- Dialog title shown when a user is asked to select vCard file -->
<string name="select_vcard_title">Select vCard file</string>
- <!-- The message shown while reading a vCard file/entry. The first argument is like
- "Reading VCard" or "Reading VCard files" and the second is the display name of the current
- data being parsed -->
- <string name="progress_shower_message"><xliff:g id="action" example="Reading VCard">%s</xliff:g>\n<xliff:g id="filename" example="foo.vcf">%s</xliff:g></string>
+ <!-- The message shown while reading vCard(s).
+ First argument is current index of contacts to be imported.
+ Second argument is the total number of contacts.
+ Third argument is the Uri which is being read. -->
+ <string name="progress_notifier_message"><xliff:g id="current_number">%s</xliff:g>/<xliff:g id="total_number">%s</xliff:g>: <xliff:g id="filename" example="foo.vcf">%s</xliff:g></string>
- <!-- Dialog title shown when reading VCard data -->
- <string name="reading_vcard_title">Reading vCard</string>
+ <!-- Dialog title shown when reading vCard data -->
+ <string name="reading_vcard_title">Reading vCard(s)</string>
- <!-- Dialog message shown when reading a VCard file -->
- <string name="reading_vcard_message">Reading vCard file(s)</string>
+ <!-- Dialog title shown when reading vCard data failed -->
+ <string name="reading_vcard_failed_title">Failed to Read vCard data</string>
- <!-- Dialog title shown when reading VCard data failed -->
- <string name="reading_vcard_failed_title">Reading of vCard data has failed</string>
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="reading_vcard_canceled_title">Reading vCard data was canceled</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>
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="reading_vcard_finished_title">Finished Reading vCard data</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>
+ <!-- The message shown when vCard importer started running. -->
+ <string name="vcard_importer_start_message">vCard importer started.</string>
+
+ <!-- The message shown when additional vCard to be imported is given during the import for others -->
+ <string name="vcard_importer_will_start_message">vCard importer will import the vCard after a while.</string>
+
+ <!-- The percentage, used for expressing the progress of vCard import. -->
+ <string name="percentage">%s%%</string>
<!-- Dialog title shown when a user confirms whether he/she export Contact data -->
<string name="confirm_export_title">Confirm export</string>
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index 85e75e6..2346467 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -21,33 +21,20 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.ContentUris;
+import android.app.Service;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
+import android.os.IBinder;
import android.os.PowerManager;
-import android.pim.vcard.VCardConfig;
-import android.pim.vcard.VCardEntryCommitter;
-import android.pim.vcard.VCardEntryConstructor;
-import android.pim.vcard.VCardEntryCounter;
-import android.pim.vcard.VCardInterpreter;
-import android.pim.vcard.VCardInterpreterCollection;
-import android.pim.vcard.VCardParser;
-import android.pim.vcard.VCardParser_V21;
-import android.pim.vcard.VCardParser_V30;
-import android.pim.vcard.VCardSourceDetector;
-import android.pim.vcard.exception.VCardException;
-import android.pim.vcard.exception.VCardNestedException;
-import android.pim.vcard.exception.VCardNotSupportedException;
-import android.pim.vcard.exception.VCardVersionException;
-import android.provider.ContactsContract.RawContacts;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
@@ -59,15 +46,12 @@
import java.io.File;
import java.io.IOException;
-import java.io.InputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Set;
import java.util.Vector;
@@ -106,25 +90,23 @@
*/
public class ImportVCardActivity extends Activity {
private static final String LOG_TAG = "ImportVCardActivity";
- private static final boolean DO_PERFORMANCE_PROFILE = false;
+
+ /* package */ static final String VCARD_URI_ARRAY = "vcard_uri_array";
// Run on the UI thread. Must not be null except after onDestroy().
private Handler mHandler = new Handler();
private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
- private Account mAccount;
+ private String mAccountName;
+ private String mAccountType;
private ProgressDialog mProgressDialogForScanVCard;
private List<VCardFile> mAllVCardFileList;
private VCardScanThread mVCardScanThread;
- private VCardReadThread mVCardReadThread;
- private ProgressDialog mProgressDialogForReadVCard;
private String mErrorMessage;
- private boolean mNeedReview = false;
-
// Runs on the UI thread.
private class DialogDisplayer implements Runnable {
private final int mResId;
@@ -153,303 +135,6 @@
private CancelListener mCancelListener = new CancelListener();
- private class VCardReadThread extends Thread
- implements DialogInterface.OnCancelListener {
- private ContentResolver mResolver;
- private VCardParser mVCardParser;
- private boolean mCanceled;
- private PowerManager.WakeLock mWakeLock;
- private Uri mUri;
- private File mTempFile;
-
- private List<VCardFile> mSelectedVCardFileList;
- private List<String> mErrorFileNameList;
-
- public VCardReadThread(Uri uri) {
- mUri = uri;
- init();
- }
-
- public VCardReadThread(final List<VCardFile> selectedVCardFileList) {
- mSelectedVCardFileList = selectedVCardFileList;
- mErrorFileNameList = new ArrayList<String>();
- init();
- }
-
- private void init() {
- Context context = ImportVCardActivity.this;
- mResolver = context.getContentResolver();
- PowerManager powerManager = (PowerManager)context.getSystemService(
- Context.POWER_SERVICE);
- mWakeLock = powerManager.newWakeLock(
- PowerManager.SCREEN_DIM_WAKE_LOCK |
- PowerManager.ON_AFTER_RELEASE, LOG_TAG);
- }
-
- @Override
- public void finalize() {
- if (mWakeLock != null && mWakeLock.isHeld()) {
- mWakeLock.release();
- }
- }
-
- @Override
- public void run() {
- boolean shouldCallFinish = true;
- mWakeLock.acquire();
- Uri createdUri = null;
- mTempFile = null;
- // Some malicious vCard data may make this thread broken
- // (e.g. OutOfMemoryError).
- // Even in such cases, some should be done.
- try {
- if (mUri != null) { // Read one vCard expressed by mUri
- final Uri targetUri = mUri;
- mProgressDialogForReadVCard.setProgressNumberFormat("");
- mProgressDialogForReadVCard.setProgress(0);
-
- // Count the number of VCard entries
- mProgressDialogForReadVCard.setIndeterminate(true);
- long start;
- if (DO_PERFORMANCE_PROFILE) {
- start = System.currentTimeMillis();
- }
- VCardEntryCounter counter = new VCardEntryCounter();
- VCardSourceDetector detector = new VCardSourceDetector();
- VCardInterpreterCollection builderCollection = new VCardInterpreterCollection(
- Arrays.asList(counter, detector));
- boolean result;
- try {
- result = readOneVCardFile(targetUri,
- VCardConfig.DEFAULT_IMPORT_CHARSET,
- builderCollection, null, true, null);
- } catch (VCardNestedException e) {
- try {
- // Assume that VCardSourceDetector was able to detect the source.
- // Try again with the detector.
- result = readOneVCardFile(targetUri,
- VCardConfig.DEFAULT_IMPORT_CHARSET,
- counter, detector, false, null);
- } 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) {
- shouldCallFinish = false;
- return;
- }
-
- mProgressDialogForReadVCard.setProgressNumberFormat(
- getString(R.string.reading_vcard_contacts));
- mProgressDialogForReadVCard.setIndeterminate(false);
- mProgressDialogForReadVCard.setMax(counter.getCount());
- String charset = detector.getEstimatedCharset();
- createdUri = doActuallyReadOneVCard(targetUri, null, charset, true, detector,
- mErrorFileNameList);
- } else { // Read multiple files.
- mProgressDialogForReadVCard.setProgressNumberFormat(
- getString(R.string.reading_vcard_files));
- mProgressDialogForReadVCard.setMax(mSelectedVCardFileList.size());
- mProgressDialogForReadVCard.setProgress(0);
-
- for (VCardFile vcardFile : mSelectedVCardFileList) {
- if (mCanceled) {
- return;
- }
- // TODO: detect scheme!
- final Uri targetUri =
- Uri.parse("file://" + vcardFile.getCanonicalPath());
-
- VCardSourceDetector detector = new VCardSourceDetector();
- try {
- if (!readOneVCardFile(targetUri,
- VCardConfig.DEFAULT_IMPORT_CHARSET,
- detector, null, true, mErrorFileNameList)) {
- continue;
- }
- } catch (VCardNestedException e) {
- // Assume that VCardSourceDetector was able to detect the source.
- }
- String charset = detector.getEstimatedCharset();
- doActuallyReadOneVCard(targetUri, mAccount,
- charset, false, detector, mErrorFileNameList);
- mProgressDialogForReadVCard.incrementProgressBy(1);
- }
- }
- } finally {
- mWakeLock.release();
- mProgressDialogForReadVCard.dismiss();
- if (mTempFile != null) {
- if (!mTempFile.delete()) {
- Log.w(LOG_TAG, "Failed to delete a cache file.");
- }
- mTempFile = null;
- }
- // finish() is called via mCancelListener, which is used in DialogDisplayer.
- if (shouldCallFinish && !isFinishing()) {
- if (mErrorFileNameList == null || mErrorFileNameList.isEmpty()) {
- finish();
- if (mNeedReview) {
- mNeedReview = false;
- Log.v("importVCardActivity", "Prepare to review the imported contact");
-
- if (createdUri != null) {
- // get contact_id of this raw_contact
- final long rawContactId = ContentUris.parseId(createdUri);
- Uri contactUri = RawContacts.getContactLookupUri(
- getContentResolver(), ContentUris.withAppendedId(
- RawContacts.CONTENT_URI, rawContactId));
-
- Intent viewIntent = new Intent(Intent.ACTION_VIEW, contactUri);
- startActivity(viewIntent);
- }
- }
- } else {
- StringBuilder builder = new StringBuilder();
- boolean first = true;
- for (String fileName : mErrorFileNameList) {
- if (first) {
- first = false;
- } else {
- builder.append(", ");
- }
- builder.append(fileName);
- }
-
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_failed_to_read_files,
- builder.toString())));
- }
- }
- }
- }
-
- private Uri doActuallyReadOneVCard(Uri uri, Account account,
- String charset, boolean showEntryParseProgress,
- VCardSourceDetector detector, List<String> errorFileNameList) {
- final Context context = ImportVCardActivity.this;
- VCardEntryConstructor builder;
- final String currentLanguage = Locale.getDefault().getLanguage();
- int vcardType = VCardConfig.getVCardTypeFromString(
- context.getString(R.string.config_import_vcard_type));
- if (charset != null) {
- builder = new VCardEntryConstructor(charset, charset, false, vcardType, mAccount);
- } else {
- charset = VCardConfig.DEFAULT_IMPORT_CHARSET;
- builder = new VCardEntryConstructor(null, null, false, vcardType, mAccount);
- }
- VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
- builder.addEntryHandler(committer);
- if (showEntryParseProgress) {
- builder.addEntryHandler(new ProgressShower(mProgressDialogForReadVCard,
- context.getString(R.string.reading_vcard_message),
- ImportVCardActivity.this,
- mHandler));
- }
-
- try {
- if (!readOneVCardFile(uri, charset, builder, detector, false, null)) {
- return null;
- }
- } catch (VCardNestedException e) {
- Log.e(LOG_TAG, "Never reach here.");
- }
- final ArrayList<Uri> createdUris = committer.getCreatedUris();
- return (createdUris == null || createdUris.size() != 1) ? null : createdUris.get(0);
- }
-
- private boolean readOneVCardFile(Uri uri, String charset,
- VCardInterpreter builder, VCardSourceDetector detector,
- boolean throwNestedException, List<String> errorFileNameList)
- throws VCardNestedException {
- InputStream is;
- try {
- is = mResolver.openInputStream(uri);
- mVCardParser = new VCardParser_V21(detector);
-
- try {
- mVCardParser.parse(is, charset, builder, mCanceled);
- } catch (VCardVersionException e1) {
- try {
- is.close();
- } catch (IOException e) {
- }
- if (builder instanceof VCardEntryConstructor) {
- // Let the object clean up internal temporal objects,
- ((VCardEntryConstructor)builder).clear();
- }
- is = mResolver.openInputStream(uri);
-
- 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) {
- }
- }
- }
- } catch (IOException e) {
- Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
-
- mProgressDialogForReadVCard.dismiss();
-
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_io_error) +
- ": " + e.getLocalizedMessage()));
- }
- return false;
- } catch (VCardNotSupportedException e) {
- if ((e instanceof VCardNestedException) && throwNestedException) {
- throw (VCardNestedException)e;
- }
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_vcard_not_supported_error) +
- " (" + e.getMessage() + ")"));
- }
- return false;
- } catch (VCardException e) {
- if (errorFileNameList != null) {
- errorFileNameList.add(uri.toString());
- } else {
- runOnUIThread(new DialogDisplayer(
- getString(R.string.fail_reason_vcard_parse_error) +
- " (" + e.getMessage() + ")"));
- }
- return false;
- }
- return true;
- }
-
- public void cancel() {
- mCanceled = true;
- if (mVCardParser != null) {
- mVCardParser.cancel();
- }
- }
-
- public void onCancel(DialogInterface dialog) {
- cancel();
- }
- }
-
private class ImportTypeSelectedListener implements
DialogInterface.OnClickListener {
public static final int IMPORT_ONE = 0;
@@ -463,7 +148,7 @@
if (which == DialogInterface.BUTTON_POSITIVE) {
switch (mCurrentIndex) {
case IMPORT_ALL:
- importMultipleVCardFromSDCard(mAllVCardFileList);
+ importVCardFromSDCard(mAllVCardFileList);
break;
case IMPORT_MULTIPLE:
showDialog(R.id.dialog_select_multiple_vcard);
@@ -496,18 +181,16 @@
if (which == DialogInterface.BUTTON_POSITIVE) {
if (mSelectedIndexSet != null) {
List<VCardFile> selectedVCardFileList = new ArrayList<VCardFile>();
- int size = mAllVCardFileList.size();
+ final int size = mAllVCardFileList.size();
// We'd like to sort the files by its index, so we do not use Set iterator.
for (int i = 0; i < size; i++) {
if (mSelectedIndexSet.contains(i)) {
selectedVCardFileList.add(mAllVCardFileList.get(i));
}
}
- importMultipleVCardFromSDCard(selectedVCardFileList);
+ importVCardFromSDCard(selectedVCardFileList);
} else {
- String canonicalPath = mAllVCardFileList.get(mCurrentIndex).getCanonicalPath();
- final Uri uri = Uri.parse("file://" + canonicalPath);
- importOneVCardFromSDCard(uri);
+ importVCardFromSDCard(mAllVCardFileList.get(mCurrentIndex));
}
} else if (which == DialogInterface.BUTTON_NEGATIVE) {
finish();
@@ -646,12 +329,9 @@
private void startVCardSelectAndImport() {
int size = mAllVCardFileList.size();
- if (getResources().getBoolean(R.bool.config_import_all_vcard_from_sdcard_automatically)) {
- importMultipleVCardFromSDCard(mAllVCardFileList);
- } else if (size == 1) {
- String canonicalPath = mAllVCardFileList.get(0).getCanonicalPath();
- Uri uri = Uri.parse("file://" + canonicalPath);
- importOneVCardFromSDCard(uri);
+ if (getResources().getBoolean(R.bool.config_import_all_vcard_from_sdcard_automatically) ||
+ size == 1) {
+ importVCardFromSDCard(mAllVCardFileList);
} else if (getResources().getBoolean(R.bool.config_allow_users_select_all_vcard_import)) {
runOnUIThread(new DialogDisplayer(R.id.dialog_select_import_type));
} else {
@@ -659,53 +339,70 @@
}
}
- private void importMultipleVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
- runOnUIThread(new Runnable() {
- public void run() {
- mVCardReadThread = new VCardReadThread(selectedVCardFileList);
- showDialog(R.id.dialog_reading_vcard);
- }
- });
+ private void importVCardFromSDCard(final List<VCardFile> selectedVCardFileList) {
+ final int size = selectedVCardFileList.size();
+ String[] uriStrings = new String[size];
+ int i = 0;
+ for (VCardFile vcardFile : selectedVCardFileList) {
+ uriStrings[i] = "file://" + vcardFile.getCanonicalPath();
+ i++;
+ }
+ importVCard(uriStrings);
+ }
+
+ private void importVCardFromSDCard(final VCardFile vcardFile) {
+ String[] uriStrings = new String[1];
+ uriStrings[0] = "file://" + vcardFile.getCanonicalPath();
+ importVCard(uriStrings);
}
- private void importOneVCardFromSDCard(final Uri uri) {
- runOnUIThread(new Runnable() {
- public void run() {
- mVCardReadThread = new VCardReadThread(uri);
- showDialog(R.id.dialog_reading_vcard);
- }
- });
+ private void importVCard(final String uriString) {
+ String[] uriStrings = new String[1];
+ uriStrings[0] = uriString;
+ importVCard(uriStrings);
+ }
+
+ private void importVCard(final String[] uriStrings) {
+ final Intent intent = new Intent(this, ImportVCardService.class);
+ intent.putExtra(VCARD_URI_ARRAY, uriStrings);
+ intent.putExtra("account_name", mAccountName);
+ intent.putExtra("account_type", mAccountType);
+
+ // TODO: permission is not migrated to ImportVCardService, so some exception is
+ // thrown when reading some Uri, permission of which is temporarily guaranteed
+ // to ImportVCardActivity, not ImportVCardService.
+ startService(intent);
+ finish();
}
private Dialog getSelectImportTypeDialog() {
- DialogInterface.OnClickListener listener =
- new ImportTypeSelectedListener();
- AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.select_vcard_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
+ final DialogInterface.OnClickListener listener = new ImportTypeSelectedListener();
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.select_vcard_title)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
- String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
+ final String[] items = new String[ImportTypeSelectedListener.IMPORT_TYPE_SIZE];
items[ImportTypeSelectedListener.IMPORT_ONE] =
- getString(R.string.import_one_vcard_string);
+ getString(R.string.import_one_vcard_string);
items[ImportTypeSelectedListener.IMPORT_MULTIPLE] =
- getString(R.string.import_multiple_vcard_string);
+ getString(R.string.import_multiple_vcard_string);
items[ImportTypeSelectedListener.IMPORT_ALL] =
- getString(R.string.import_all_vcard_string);
+ getString(R.string.import_all_vcard_string);
builder.setSingleChoiceItems(items, ImportTypeSelectedListener.IMPORT_ONE, listener);
return builder.create();
}
private Dialog getVCardFileSelectDialog(boolean multipleSelect) {
- int size = mAllVCardFileList.size();
- VCardSelectedListener listener = new VCardSelectedListener(multipleSelect);
- AlertDialog.Builder builder =
- new AlertDialog.Builder(this)
- .setTitle(R.string.select_vcard_title)
- .setPositiveButton(android.R.string.ok, listener)
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
+ final int size = mAllVCardFileList.size();
+ final VCardSelectedListener listener = new VCardSelectedListener(multipleSelect);
+ final AlertDialog.Builder builder =
+ new AlertDialog.Builder(this)
+ .setTitle(R.string.select_vcard_title)
+ .setPositiveButton(android.R.string.ok, listener)
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
CharSequence[] items = new CharSequence[size];
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -739,17 +436,14 @@
final Intent intent = getIntent();
if (intent != null) {
- final String accountName = intent.getStringExtra("account_name");
- final String accountType = intent.getStringExtra("account_type");
- if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
- mAccount = new Account(accountName, accountType);
- }
+ mAccountName = intent.getStringExtra("account_name");
+ mAccountType = intent.getStringExtra("account_type");
} else {
Log.e(LOG_TAG, "intent does not exist");
}
// The caller often does not know account information at all, so we show the UI instead.
- if (mAccount == null) {
+ if (TextUtils.isEmpty(mAccountName) || TextUtils.isEmpty(mAccountType)) {
// There's three possibilities:
// - more than one accounts -> ask the user
// - just one account -> use the account without asking the user
@@ -765,7 +459,9 @@
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
- mAccount = mAccountList.get(which);
+ final Account account = mAccountList.get(which);
+ mAccountName = account.name;
+ mAccountType = account.type;
// Instead of using Intent mechanism, call the relevant private method,
// to avoid throwing an Intent to itself again.
startImport();
@@ -774,7 +470,11 @@
showDialog(resId);
return;
} else {
- mAccount = size > 0 ? accountList.get(0) : null;
+ final Account account = ((size > 0) ? accountList.get(0) : null);
+ if (account != null) {
+ mAccountName = account.name;
+ mAccountType = account.type;
+ }
}
}
@@ -786,13 +486,9 @@
final String action = intent.getAction();
final Uri uri = intent.getData();
Log.v(LOG_TAG, "action = " + action + " ; path = " + uri);
- if (Intent.ACTION_VIEW.equals(action)) {
- // Import the file directly and then go to EDIT screen
- mNeedReview = true;
- }
if (uri != null) {
- importOneVCardFromSDCard(uri);
+ importVCard(uri.toString());
} else {
doScanExternalStorageAndImportVCard();
}
@@ -849,19 +545,6 @@
case R.id.dialog_select_one_vcard: {
return getVCardFileSelectDialog(false);
}
- case R.id.dialog_reading_vcard: {
- if (mProgressDialogForReadVCard == null) {
- String title = getString(R.string.reading_vcard_title);
- String message = getString(R.string.reading_vcard_message);
- mProgressDialogForReadVCard = new ProgressDialog(this);
- mProgressDialogForReadVCard.setTitle(title);
- mProgressDialogForReadVCard.setMessage(message);
- mProgressDialogForReadVCard.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- mProgressDialogForReadVCard.setOnCancelListener(mVCardReadThread);
- mVCardReadThread.start();
- }
- return mProgressDialogForReadVCard;
- }
case R.id.dialog_io_exception: {
String message = (getString(R.string.scanning_sdcard_failed_message,
getString(R.string.fail_reason_io_error)));
@@ -895,11 +578,6 @@
@Override
protected void onPause() {
super.onPause();
- if (mVCardReadThread != null) {
- // The Activity is no longer visible. Stop the thread.
- mVCardReadThread.cancel();
- mVCardReadThread = null;
- }
// ImportVCardActivity should not be persistent. In other words, if there's some
// event calling onPause(), this Activity should finish its work and give the main
@@ -916,29 +594,6 @@
// make sure that the handler does not run any callback when
// this activity isFinishing().
- // Need to make sure any worker thread is done before we flush and
- // nullify the message handler.
- if (mVCardReadThread != null) {
- Log.w(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
- mVCardReadThread.cancel();
- int attempts = 0;
- while (mVCardReadThread.isAlive() && attempts < 10) {
- try {
- Thread.currentThread().sleep(20);
- } catch (InterruptedException ie) {
- // Keep on going until max attempts is reached.
- }
- attempts++;
- }
- if (mVCardReadThread.isAlive()) {
- // Find out why the thread did not exit in a timely
- // fashion. Last resort: increase the sleep duration
- // and/or the number of attempts.
- Log.e(LOG_TAG, "VCardReadThread is still alive after max attempts.");
- }
- mVCardReadThread = null;
- }
-
// Callbacks messages have what == 0.
if (mHandler.hasMessages(0)) {
mHandler.removeMessages(0);
@@ -959,17 +614,6 @@
}
}
- @Override
- public void finalize() {
- // TODO: This should not be needed. Throw exception instead.
- if (mVCardReadThread != null) {
- // Not sure this procedure is really needed, but just in case...
- Log.e(LOG_TAG, "VCardReadThread exists while this Activity is now being killed!");
- mVCardReadThread.cancel();
- mVCardReadThread = null;
- }
- }
-
/**
* Scans vCard in external storage (typically SDCard) and tries to import it.
* - When there's no SDCard available, an error dialog is shown.
diff --git a/src/com/android/contacts/ImportVCardService.java b/src/com/android/contacts/ImportVCardService.java
new file mode 100644
index 0000000..aea47cb
--- /dev/null
+++ b/src/com/android/contacts/ImportVCardService.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright (C) 2010 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.accounts.Account;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.IBinder;
+import android.pim.vcard.VCardConfig;
+import android.pim.vcard.VCardEntry;
+import android.pim.vcard.VCardEntryCommitter;
+import android.pim.vcard.VCardEntryConstructor;
+import android.pim.vcard.VCardEntryCounter;
+import android.pim.vcard.VCardEntryHandler;
+import android.pim.vcard.VCardInterpreter;
+import android.pim.vcard.VCardInterpreterCollection;
+import android.pim.vcard.VCardParser;
+import android.pim.vcard.VCardParser_V21;
+import android.pim.vcard.VCardParser_V30;
+import android.pim.vcard.VCardSourceDetector;
+import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardNestedException;
+import android.pim.vcard.exception.VCardNotSupportedException;
+import android.pim.vcard.exception.VCardVersionException;
+import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * The class responsible for importing vCard from one ore multiple Uris.
+ */
+public class ImportVCardService extends Service {
+ private final static String LOG_TAG = "ImportVCardService";
+
+ private class ProgressNotifier implements VCardEntryHandler {
+ private final int mId;
+
+ public ProgressNotifier(int id) {
+ mId = id;
+ }
+
+ public void onStart() {
+ }
+
+ public void onEntryCreated(VCardEntry contactStruct) {
+ mCurrentCount++; // 1 origin.
+ if (contactStruct.isIgnorable()) {
+ return;
+ }
+
+ final Context context = ImportVCardService.this;
+ // We don't use startEntry() since:
+ // - We cannot know name there but here.
+ // - There's high probability where name comes soon after the beginning of entry, so
+ // we don't need to hurry to show something.
+ final String packageName = "com.android.contacts";
+ final RemoteViews remoteViews = new RemoteViews(packageName,
+ R.layout.status_bar_ongoing_event_progress_bar);
+ final String title = getString(R.string.reading_vcard_title);
+ final String text = getString(R.string.progress_notifier_message,
+ String.valueOf(mCurrentCount),
+ String.valueOf(mTotalCount),
+ contactStruct.getDisplayName());
+
+ // TODO: uploading image does not work correctly. (looks like a static image).
+ remoteViews.setTextViewText(R.id.description, text);
+ remoteViews.setProgressBar(R.id.progress_bar, mTotalCount, mCurrentCount,
+ mTotalCount == -1);
+ final String percentage =
+ getString(R.string.percentage,
+ String.valueOf(mCurrentCount * 100/mTotalCount));
+ remoteViews.setTextViewText(R.id.progress_text, percentage);
+ remoteViews.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_download);
+
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_download;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+ notification.contentView = remoteViews;
+
+ notification.contentIntent =
+ PendingIntent.getActivity(context, 0,
+ new Intent(context, ContactsListActivity.class), 0);
+ mNotificationManager.notify(mId, notification);
+ }
+
+ public void onEnd() {
+ }
+ }
+
+ private class VCardReadThread extends Thread {
+ private final Context mContext;
+ private final ContentResolver mResolver;
+ private final int mPreferedVCardType;
+ private VCardParser mVCardParser;
+ private boolean mCanceled;
+ private final List<Uri> mErrorUris;
+ private final List<Uri> mCreatedUris;
+
+ public VCardReadThread() {
+ mContext = ImportVCardService.this;
+ mResolver = mContext.getContentResolver();
+ mPreferedVCardType = VCardConfig.getVCardTypeFromString(
+ mContext.getString(R.string.config_import_vcard_type));
+ mErrorUris = new ArrayList<Uri>();
+ mCreatedUris = new ArrayList<Uri>();
+ }
+
+ @Override
+ public void run() {
+ while (!mCanceled) {
+ final Account account;
+ final Uri[] uris;
+ final int id;
+ final boolean needReview;
+ synchronized (mContext) {
+ if (mPendingInputs.size() == 0) {
+ mNowRunning = false;
+ break;
+ } else {
+ final PendingInput pendingInput = mPendingInputs.poll();
+ account = pendingInput.account;
+ uris = pendingInput.uris;
+ id = pendingInput.id;
+ }
+ }
+ runInternal(account, uris, id);
+ doFinishNotification(id, uris);
+ mErrorUris.clear();
+ mCreatedUris.clear();
+ }
+ Log.i(LOG_TAG, "Successfully imported. Total: " + mTotalCount);
+ stopSelf();
+ }
+
+ private void runInternal(Account account, Uri[] uris, int id) {
+ int totalCount = 0;
+ final ArrayList<VCardSourceDetector> detectorList =
+ new ArrayList<VCardSourceDetector>();
+ final String defaultCharset = VCardConfig.DEFAULT_IMPORT_CHARSET;
+ // First scan all Uris with a default charset and try to understand an exact
+ // charset to be used to each Uri. Note that detector would return null when
+ // it does not know an appropriate charset, so stick to use the default
+ // at that time.
+ // TODO: notification for first scanning?
+ for (Uri uri : uris) {
+ if (mCanceled) {
+ return;
+ }
+ final VCardEntryCounter counter = new VCardEntryCounter();
+ final VCardSourceDetector detector = new VCardSourceDetector();
+ final VCardInterpreterCollection interpreterCollection =
+ new VCardInterpreterCollection(Arrays.asList(counter, detector));
+ try {
+ if (!readOneVCard(uri, defaultCharset,
+ interpreterCollection, null, true)) {
+ mErrorUris.add(uri);
+ }
+ } catch (VCardNestedException e) {
+ // Assume that VCardSourceDetector was able to detect the source.
+ }
+
+ totalCount += counter.getCount();
+ detectorList.add(detector);
+ }
+
+ if (mErrorUris.size() > 0) {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Error happened on ");
+ for (Uri errorUri : mErrorUris) {
+ builder.append("\"");
+ builder.append(errorUri.toString());
+ builder.append("\"");
+ }
+ Log.e(LOG_TAG, builder.toString());
+ doErrorNotification(id);
+ return;
+ }
+
+ if (uris.length != detectorList.size()) {
+ Log.e(LOG_TAG,
+ "The number of Uris to be imported is different from that of " +
+ "charset to be used.");
+ doErrorNotification(id);
+ return;
+ }
+
+ // First scanning is over. Try to import each vCard, which causes side effects.
+ mTotalCount = totalCount;
+ mCurrentCount = 0;
+
+ for (int i = 0; i < uris.length; i++) {
+ if (mCanceled) {
+ Log.w(LOG_TAG, "Canceled during importing (with storing data in database)");
+ // TODO: implement cancel correctly.
+ return;
+ }
+ final Uri uri = uris[i];
+ final VCardSourceDetector detector = detectorList.get(i);
+ final int vcardType = mPreferedVCardType;
+ String charset = detector.getEstimatedCharset();
+ if (charset == null) {
+ charset = defaultCharset;
+ }
+ final VCardEntryConstructor constructor =
+ new VCardEntryConstructor(charset, charset, false, vcardType, account);
+ final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
+ final ProgressNotifier notifier = new ProgressNotifier(id);
+ constructor.addEntryHandler(committer);
+ constructor.addEntryHandler(notifier);
+
+ try {
+ if (!readOneVCard(uri, charset, constructor, detector, false)) {
+ Log.e(LOG_TAG, "Failed to read \"" + uri.toString() + "\" " +
+ "while first scan was successful.");
+ }
+ } catch (VCardNestedException e) {
+ // We should already know the number of nests in the first scan and
+ // treat them at this time.
+ Log.e(LOG_TAG, "Must not reach here.");
+ }
+ final List<Uri> createdUris = committer.getCreatedUris();
+ if (createdUris != null && createdUris.size() > 0) {
+ mCreatedUris.addAll(createdUris);
+ } else {
+ Log.w(LOG_TAG, "Created Uris is null (src = " + uri.toString() + "\"");
+ }
+ }
+ }
+
+ private boolean readOneVCard(Uri uri, String charset, VCardInterpreter interpreter,
+ VCardSourceDetector detector, boolean throwNestedException)
+ throws VCardNestedException {
+ InputStream is;
+ try {
+ is = mResolver.openInputStream(uri);
+
+ // We need synchronized since we need to handle mCanceled and mVCardParser
+ // at once. In the worst case, a user may call cancel() just before recreating
+ // mVCardParser.
+ synchronized (this) {
+ mVCardParser = new VCardParser_V21(detector);
+ if (mCanceled) {
+ mVCardParser.cancel();
+ }
+ }
+
+ try {
+ mVCardParser.parse(is, charset, interpreter);
+ } catch (VCardVersionException e1) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ if (interpreter instanceof VCardEntryConstructor) {
+ // Let the object clean up internal temporal objects,
+ ((VCardEntryConstructor) interpreter).clear();
+ }
+ is = mResolver.openInputStream(uri);
+
+ synchronized (this) {
+ mVCardParser = new VCardParser_V30();
+ if (mCanceled) {
+ mVCardParser.cancel();
+ }
+ }
+
+ try {
+ mVCardParser.parse(is, charset, interpreter);
+ } catch (VCardVersionException e2) {
+ throw new VCardException("vCard with unspported version.");
+ }
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
+ return false;
+ } catch (VCardNotSupportedException e) {
+ if ((e instanceof VCardNestedException) && throwNestedException) {
+ throw (VCardNestedException) e;
+ }
+ return false;
+ } catch (VCardException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private void doErrorNotification(int id) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_download_done;
+ final String title = mContext.getString(R.string.reading_vcard_failed_title);
+ final PendingIntent intent =
+ PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ notification.setLatestEventInfo(mContext, title, "", intent);
+ mNotificationManager.notify(id, notification);
+ }
+
+ private void doFinishNotification(int id, Uri[] uris) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_download_done;
+ final String title = mContext.getString(R.string.reading_vcard_finished_title);
+
+ final Intent intent;
+ final long rawContactId = ContentUris.parseId(mCreatedUris.get(0));
+ final Uri contactUri = RawContacts.getContactLookupUri(
+ getContentResolver(), ContentUris.withAppendedId(
+ RawContacts.CONTENT_URI, rawContactId));
+ intent = new Intent(Intent.ACTION_VIEW, contactUri);
+
+ final String text = ((uris.length == 1) ? uris[0].getPath() : "");
+ final PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, intent, 0);
+ notification.setLatestEventInfo(mContext, title, text, pendingIntent);
+ mNotificationManager.notify(id, notification);
+ }
+
+ // We need synchronized since we need to handle mCanceled and mVCardParser at once.
+ public synchronized void cancel() {
+ mCanceled = true;
+ if (mVCardParser != null) {
+ mVCardParser.cancel();
+ }
+ }
+
+ public void onCancel(DialogInterface dialog) {
+ cancel();
+ }
+ }
+
+ private static class PendingInput {
+ public final Account account;
+ public final Uri[] uris;
+ public final int id;
+
+ public PendingInput(Account account, Uri[] uris, int id) {
+ this.account = account;
+ this.uris = uris;
+ this.id = id;
+ }
+ }
+
+ // The two classes bellow must be called inside the synchronized block, using this context.
+ private boolean mNowRunning;
+ private final Queue<PendingInput> mPendingInputs = new LinkedList<PendingInput>();
+
+ private NotificationManager mNotificationManager;
+ private Thread mThread;
+ private int mTotalCount;
+ private int mCurrentCount;
+
+ private Uri[] tryGetUris(Intent intent) {
+ final String[] uriStrings =
+ intent.getStringArrayExtra(ImportVCardActivity.VCARD_URI_ARRAY);
+ if (uriStrings == null || uriStrings.length == 0) {
+ Log.e(LOG_TAG, "Given uri array is empty");
+ return null;
+ }
+
+ final int length = uriStrings.length;
+ final Uri[] uris = new Uri[length];
+ for (int i = 0; i < length; i++) {
+ uris[i] = Uri.parse(uriStrings[i]);
+ }
+
+ return uris;
+ }
+
+ private Account tryGetAccount(Intent intent) {
+ if (intent == null) {
+ Log.w(LOG_TAG, "Intent is null");
+ return null;
+ }
+
+ final String accountName = intent.getStringExtra("account_name");
+ final String accountType = intent.getStringExtra("account_type");
+ if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+ return new Account(accountName, accountType);
+ } else {
+ Log.w(LOG_TAG, "Account is not set.");
+ return null;
+ }
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (mNotificationManager == null) {
+ mNotificationManager =
+ (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ final Account account = tryGetAccount(intent);
+ final Uri[] uris = tryGetUris(intent);
+ if (uris == null) {
+ Log.e(LOG_TAG, "Uris are null.");
+ Toast.makeText(this, getString(R.string.reading_vcard_failed_title),
+ Toast.LENGTH_LONG).show();
+ stopSelf();
+ return START_NOT_STICKY;
+ }
+
+ synchronized (this) {
+ mPendingInputs.add(new PendingInput(account, uris, startId));
+ if (!mNowRunning) {
+ Toast.makeText(this, getString(R.string.vcard_importer_start_message),
+ Toast.LENGTH_LONG).show();
+ // Assume thread is alredy broken.
+ // Even when it still exists, it never scan the PendingInput newly added above.
+ mNowRunning = true;
+ mThread = new VCardReadThread();
+ mThread.start();
+ } else {
+ Toast.makeText(this, getString(R.string.vcard_importer_will_start_message),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+}
diff --git a/src/com/android/contacts/ProgressShower.java b/src/com/android/contacts/ProgressShower.java
deleted file mode 100644
index a5ad2a2..0000000
--- a/src/com/android/contacts/ProgressShower.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.ProgressDialog;
-import android.content.Context;
-import android.os.Handler;
-import android.pim.vcard.VCardEntry;
-import android.pim.vcard.VCardEntryHandler;
-import android.pim.vcard.VCardConfig;
-import android.util.Log;
-
-public class ProgressShower implements VCardEntryHandler {
- public static final String LOG_TAG = "vcard.ProgressShower";
-
- private final Context mContext;
- private final Handler mHandler;
- private final ProgressDialog mProgressDialog;
- private final String mProgressMessage;
-
- private long mTime;
-
- private class ShowProgressRunnable implements Runnable {
- private VCardEntry mContact;
-
- public ShowProgressRunnable(VCardEntry contact) {
- mContact = contact;
- }
-
- public void run() {
- mProgressDialog.setMessage( mProgressMessage + "\n" +
- mContact.getDisplayName());
- mProgressDialog.incrementProgressBy(1);
- }
- }
-
- public ProgressShower(ProgressDialog progressDialog,
- String progressMessage,
- Context context,
- Handler handler) {
- mContext = context;
- mHandler = handler;
- mProgressDialog = progressDialog;
- mProgressMessage = progressMessage;
- }
-
- public void onStart() {
- }
-
- public void onEntryCreated(VCardEntry contactStruct) {
- long start = System.currentTimeMillis();
-
- if (!contactStruct.isIgnorable()) {
- if (mProgressDialog != null && mProgressMessage != null) {
- if (mHandler != null) {
- mHandler.post(new ShowProgressRunnable(contactStruct));
- } else {
- mProgressDialog.setMessage(mContext.getString(R.string.progress_shower_message,
- mProgressMessage,
- contactStruct.getDisplayName()));
- }
- }
- }
-
- mTime += System.currentTimeMillis() - start;
- }
-
- public void onEnd() {
- if (VCardConfig.showPerformanceLog()) {
- Log.d(LOG_TAG,
- String.format("Time to progress a dialog: %d ms", mTime));
- }
- }
-}