Refactoring vCard importer/exporter.
Rename ImportVCardService to VCardService and make it responsible for all the import/export works.
The service itself asks ImportProcessor/ExportProcessor to handle each request.
This implementation seems much more easier to be tested than the previous ones.
Bug: 2733143
Change-Id: I7b7d391e4ba294f74c8bbcdfb053368b61b498ca
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 48ff0da..24dfe86 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -535,13 +535,13 @@
<activity android:name=".vcard.SelectAccountActivity"
android:theme="@style/BackgroundOnly" />
- <service
- android:name=".vcard.ImportVCardService"
- android:exported="false" />
-
<activity android:name=".vcard.ExportVCardActivity"
android:theme="@style/BackgroundOnly" />
+ <service
+ android:name=".vcard.VCardService"
+ android:exported="false" />
+
<!-- Pinned header list demo -->
<activity android:name=".widget.PinnedHeaderListDemoActivity">
<intent-filter>
diff --git a/res/values/donottranslate_config.xml b/res/values/donottranslate_config.xml
index f1c6951..cad2a63 100644
--- a/res/values/donottranslate_config.xml
+++ b/res/values/donottranslate_config.xml
@@ -53,7 +53,7 @@
<string name="config_export_vcard_type" translatable="false">default</string>
<!-- Directory in which exported VCard file is stored -->
- <string name="config_export_dir" translatable="false">/sdcard</string>
+ <string name="config_export_dir" translatable="false">/mnt/sdcard</string>
<!-- Prefix of exported VCard file -->
<string name="config_export_file_prefix" translatable="false"></string>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 90f8df6..1b07bff 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -748,7 +748,7 @@
data storage. -->
<string name="caching_vcard_message">Importer is caching vCard(s) to local temporary storage. Actual import will start soon.</string>
- <!-- The message shown while reading vCard(s).
+ <!-- The message shown while importing 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. -->
@@ -764,7 +764,7 @@
<string name="reading_vcard_canceled_title">Reading vCard data was canceled</string>
<!-- The title shown when reading vCard is canceled (probably by a user) -->
- <string name="reading_vcard_finished_title">Finished Reading vCard data</string>
+ <string name="importing_vcard_finished_title">Finished importing vCard</string>
<!-- The message shown when vCard importer started running. -->
<string name="vcard_importer_start_message">vCard importer started.</string>
@@ -772,7 +772,7 @@
<!-- 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. -->
+ <!-- The percentage, used for expressing the progress of vCard import/export. -->
<string name="percentage">%s%%</string>
<!-- Dialog title shown when a user confirms whether he/she export Contact data -->
@@ -801,6 +801,12 @@
mention it here. -->
<string name="fail_reason_too_long_filename">Required filename is too long (\"<xliff:g id="filename">%s</xliff:g>\")</string>
+ <!-- The message shown when vCard importer started running. -->
+ <string name="vcard_exporter_start_message">vCard exporter started.</string>
+
+ <!-- The title shown when reading vCard is canceled (probably by a user) -->
+ <string name="exporting_vcard_finished_title">Finished exporting vCard</string>
+
<!-- Dialog title shown when the application is exporting contact data outside -->
<string name="exporting_contact_list_title">Exporting contact data</string>
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
new file mode 100644
index 0000000..15f6fd8
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -0,0 +1,254 @@
+/*
+ * 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.vcard;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import com.android.contacts.ContactsListActivity;
+import com.android.contacts.R;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.Queue;
+
+public class ExportProcessor {
+ private static final String LOG_TAG = "ExportProcessor";
+
+ private final Service mService;
+
+ private ContentResolver mResolver;
+ private NotificationManager mNotificationManager;
+
+ boolean mCanceled;
+
+ boolean mReadyForRequest;
+ private final Queue<ExportRequest> mPendingRequests =
+ new LinkedList<ExportRequest>();
+
+ private RemoteViews mProgressViews;
+
+ public ExportProcessor(Service service) {
+ mService = service;
+ }
+
+ /* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
+ public void start() {
+ final Thread thread = new Thread(new Runnable() {
+ public void run() {
+ process();
+ }
+ });
+ thread.start();
+ }
+ };
+
+ public synchronized void pushRequest(ExportRequest parameter) {
+ if (mResolver == null) {
+ // Service object may not ready at the construction time
+ // (e.g. ContentResolver may be null).
+ mResolver = mService.getContentResolver();
+ mNotificationManager =
+ (NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ final boolean needThreadStart;
+ if (!mReadyForRequest) {
+ needThreadStart = true;
+ } else {
+ needThreadStart = false;
+ }
+ mPendingRequests.add(parameter);
+ if (needThreadStart) {
+ mThreadStarter.start();
+ }
+
+ mReadyForRequest = true;
+ }
+
+ /* package */ void process() {
+ if (!mReadyForRequest) {
+ throw new RuntimeException(
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
+ }
+
+ try {
+ while (!mCanceled) {
+ final ExportRequest parameter;
+ synchronized (this) {
+ if (mPendingRequests.size() == 0) {
+ mReadyForRequest = false;
+ break;
+ } else {
+ parameter = mPendingRequests.poll();
+ }
+ } // synchronized (this)
+ handleOneRequest(parameter);
+ }
+
+ doFinishNotification(mService.getString(R.string.exporting_vcard_finished_title),
+ "");
+ } finally {
+ // Not thread safe. Just in case.
+ // TODO: verify this works fine.
+ mReadyForRequest = false;
+ }
+ }
+
+ /* package */ void handleOneRequest(ExportRequest request) {
+ boolean shouldCallFinish = true;
+ VCardComposer composer = null;
+ try {
+ final Uri uri = request.destUri;
+ final OutputStream outputStream;
+ try {
+ outputStream = mResolver.openOutputStream(uri);
+ } catch (FileNotFoundException e) {
+ // Need concise title.
+
+ final String errorReason =
+ mService.getString(R.string.fail_reason_could_not_open_file,
+ uri, e.getMessage());
+ shouldCallFinish = false;
+ doFinishNotification(errorReason, "");
+ return;
+ }
+ final String exportType = request.exportType;
+ final int vcardType;
+ if (TextUtils.isEmpty(exportType)) {
+ vcardType = VCardConfig.getVCardTypeFromString(
+ mService.getString(R.string.config_export_vcard_type));
+ } else {
+ vcardType = VCardConfig.getVCardTypeFromString(exportType);
+ }
+
+ composer = new VCardComposer(mService, vcardType, true);
+
+ // for test
+ // int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
+ // VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
+ // composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
+
+ composer.addHandler(composer.new HandlerForOutputStream(outputStream));
+
+ if (!composer.init()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ final String title =
+ mService.getString(R.string.fail_reason_could_not_initialize_exporter,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ final int total = composer.getCount();
+ if (total == 0) {
+ final String title =
+ mService.getString(R.string.fail_reason_no_exportable_contact);
+ doFinishNotification(title, "");
+ return;
+ }
+
+ int current = 1; // 1-origin
+ while (!composer.isAfterLast()) {
+ if (mCanceled) {
+ return;
+ }
+ if (!composer.createOneEntry()) {
+ final String errorReason = composer.getErrorReason();
+ Log.e(LOG_TAG, "Failed to read a contact: " + errorReason);
+ final String translatedErrorReason =
+ translateComposerError(errorReason);
+ final String title =
+ mService.getString(R.string.fail_reason_error_occurred_during_export,
+ translatedErrorReason);
+ doFinishNotification(title, "");
+ return;
+ }
+ doProgressNotification(uri, total, current);
+ }
+ } finally {
+ if (composer != null) {
+ composer.terminate();
+ }
+ }
+ }
+
+ private String translateComposerError(String errorMessage) {
+ final Resources resources = mService.getResources();
+ if (VCardComposer.FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO.equals(errorMessage)) {
+ return resources.getString(R.string.composer_failed_to_get_database_infomation);
+ } else if (VCardComposer.FAILURE_REASON_NO_ENTRY.equals(errorMessage)) {
+ return resources.getString(R.string.composer_has_no_exportable_contact);
+ } else if (VCardComposer.FAILURE_REASON_NOT_INITIALIZED.equals(errorMessage)) {
+ return resources.getString(R.string.composer_not_initialized);
+ } else {
+ return errorMessage;
+ }
+ }
+
+ private void doProgressNotification(Uri uri, int total, int current) {
+ final String title = mService.getString(R.string.exporting_contact_list_title);
+ final String message =
+ mService.getString(R.string.exporting_contact_list_message, uri);
+
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ final RemoteViews remoteView = new RemoteViews(mService.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteView.setTextViewText(R.id.description, message);
+ remoteView.setProgressBar(R.id.progress_bar, total, current, (total == -1));
+ final String percentage = mService.getString(R.string.percentage,
+ String.valueOf((current * 100)/total));
+ remoteView.setTextViewText(R.id.progress_text, percentage);
+ remoteView.setImageViewResource(R.id.appIcon, android.R.drawable.stat_sys_download);
+ notification.contentView = remoteView;
+
+ final Intent intent = new Intent(mService, ContactsListActivity.class);
+ notification.contentIntent =
+ PendingIntent.getActivity(mService, 0, intent, 0);
+
+ mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ }
+
+ private void doFinishNotification(final String title, final String message) {
+ final Notification notification = new Notification();
+ notification.icon = android.R.drawable.stat_sys_upload_done;
+ notification.setLatestEventInfo(mService, title, message, null);
+ final Intent intent = new Intent(mService, ContactsListActivity.class);
+ notification.contentIntent =
+ PendingIntent.getActivity(mService, 0, intent, 0);
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportRequest.java b/src/com/android/contacts/vcard/ExportRequest.java
new file mode 100644
index 0000000..fae2d07
--- /dev/null
+++ b/src/com/android/contacts/vcard/ExportRequest.java
@@ -0,0 +1,35 @@
+/*
+ * 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.vcard;
+
+import android.net.Uri;
+
+public class ExportRequest {
+ public final Uri destUri;
+ /**
+ * Can be null.
+ */
+ public final String exportType;
+
+ public ExportRequest(Uri destUri) {
+ this(destUri, null);
+ }
+
+ public ExportRequest(Uri destUri, String exportType) {
+ this.destUri = destUri;
+ this.exportType = exportType;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportVCardActivity.java b/src/com/android/contacts/vcard/ExportVCardActivity.java
index 509702b..081feb1 100644
--- a/src/com/android/contacts/vcard/ExportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ExportVCardActivity.java
@@ -19,12 +19,21 @@
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.res.Resources;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
import android.os.PowerManager;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
@@ -37,6 +46,8 @@
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Queue;
import java.util.Set;
/**
@@ -74,6 +85,67 @@
private ActualExportThread mActualExportThread;
+ private class CustomConnection implements ServiceConnection {
+ private Messenger mMessenger;
+ private Queue<ExportRequest> mPendingRequests = new LinkedList<ExportRequest>();
+
+ public void doBindService() {
+ bindService(new Intent(ExportVCardActivity.this,
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
+ }
+
+ public synchronized void requestSend(final ExportRequest parameter) {
+ if (mMessenger != null) {
+ sendMessage(parameter);
+ } else {
+ mPendingRequests.add(parameter);
+ }
+ }
+
+ private void sendMessage(final ExportRequest request) {
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_EXPORT_REQUEST,
+ request));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new ErrorReasonDisplayer(
+ getString(R.string.fail_reason_unknown)));
+ }
+ }
+
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ mMessenger = new Messenger(service);
+ // Send pending requests thrown from this Activity before an actual connection
+ // is established.
+ while (!mPendingRequests.isEmpty()) {
+ final ExportRequest parameter = mPendingRequests.poll();
+ if (parameter == null) {
+ throw new NullPointerException();
+ }
+ sendMessage(parameter);
+ }
+ unbindService(this);
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ if (!mPendingRequests.isEmpty()) {
+ Log.w(LOG_TAG, "Some request(s) are dropped.");
+ }
+ // Set to null so that we can detect inappropriate re-connection toward
+ // the Service via NullPointerException;
+ mPendingRequests = null;
+ mMessenger = null;
+ finish();
+ }
+ }
+ }
+
+ private final CustomConnection mConnection = new CustomConnection();
+
private class CancelListener
implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
public void onClick(DialogInterface dialog, int which) {
@@ -104,16 +176,24 @@
}
private class ExportConfirmationListener implements DialogInterface.OnClickListener {
- private final String mFileName;
+ private final Uri mDestUri;
public ExportConfirmationListener(String fileName) {
- mFileName = fileName;
+ this(Uri.parse("file://" + fileName));
+ }
+
+ public ExportConfirmationListener(Uri uri) {
+ mDestUri = uri;
}
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- mActualExportThread = new ActualExportThread(mFileName);
- showDialog(R.id.dialog_exporting_vcard);
+ mConnection.doBindService();
+
+ final ExportRequest request = new ExportRequest(mDestUri);
+
+ // The connection object will call finish().
+ mConnection.requestSend(request);
}
}
}
@@ -151,9 +231,11 @@
final int vcardType = VCardConfig.getVCardTypeFromString(mVCardTypeStr);
composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
- /*int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
- VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
- composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);*/
+
+ // for testing
+ // int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
+ // VCardConfig.FLAG_USE_QP_TO_PRIMARY_PROPERTIES);
+ // composer = new VCardComposer(ExportVCardActivity.this, vcardType, true);
composer.addHandler(composer.new HandlerForOutputStream(outputStream));
@@ -169,7 +251,7 @@
return;
}
- int size = composer.getCount();
+ final int size = composer.getCount();
if (size == 0) {
mHandler.post(new ErrorReasonDisplayer(
@@ -273,7 +355,7 @@
}
@Override
- protected Dialog onCreateDialog(int id) {
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
switch (id) {
case R.id.dialog_export_confirmation: {
return getExportConfirmationDialog();
@@ -312,18 +394,18 @@
return mProgressDialog;
}
}
- return super.onCreateDialog(id);
+ return super.onCreateDialog(id, bundle);
}
@Override
- protected void onPrepareDialog(int id, Dialog dialog) {
+ protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
if (id == R.id.dialog_fail_to_export_with_reason) {
((AlertDialog)dialog).setMessage(getErrorReason());
} else if (id == R.id.dialog_export_confirmation) {
((AlertDialog)dialog).setMessage(
getString(R.string.confirm_export_message, mTargetFileName));
} else {
- super.onPrepareDialog(id, dialog);
+ super.onPrepareDialog(id, dialog, args);
}
}
diff --git a/src/com/android/contacts/vcard/ImportRequestProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
similarity index 94%
rename from src/com/android/contacts/vcard/ImportRequestProcessor.java
rename to src/com/android/contacts/vcard/ImportProcessor.java
index b4158b6..be7d2ea 100644
--- a/src/com/android/contacts/vcard/ImportRequestProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -53,7 +53,7 @@
* This class is designed so that a user ({@link Service}) does not need to (and should not)
* recreate multiple instances, as this holds total count of vCard entries to be imported.
*/
-public class ImportRequestProcessor {
+public class ImportProcessor {
private static final String LOG_TAG = "ImportRequestProcessor";
private final Service mService;
@@ -67,19 +67,6 @@
private VCardParser mVCardParser;
- // TODO(dmiyakawa): better design for testing?
- /* package */ interface CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver);
- }
-
- private static class DefaultCommitterGenerator implements CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver) {
- return new VCardEntryCommitter(resolver);
- }
- }
-
- /* package */ CommitterGenerator mCommitterGenerator = new DefaultCommitterGenerator();
-
/**
* Meaning a controller of this object requests the operation should be canceled
* or not, which implies {@link #mReadyForRequest} should be set to false soon, but
@@ -95,9 +82,7 @@
private final Queue<ImportRequest> mPendingRequests =
new LinkedList<ImportRequest>();
- /* package */ interface ThreadStarter {
- public void start();
- }
+ // For testability.
/* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
public void start() {
final Thread thread = new Thread(new Runnable() {
@@ -108,9 +93,17 @@
thread.start();
}
};
+ /* package */ interface CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver);
+ }
+ /* package */ class DefaultCommitterGenerator implements CommitterGenerator {
+ public VCardEntryCommitter generate(ContentResolver resolver) {
+ return new VCardEntryCommitter(mResolver);
+ }
+ }
+ /* package */ CommitterGenerator mCommitterGenerator = new DefaultCommitterGenerator();
-
- public ImportRequestProcessor(final Service service) {
+ public ImportProcessor(final Service service) {
mService = service;
}
@@ -153,7 +146,8 @@
/* package */ void process() {
if (!mReadyForRequest) {
throw new RuntimeException(
- "process() is called after this object finishing its process.");
+ "process() is called before request being pushed "
+ + "or after this object's finishing its processing.");
}
try {
while (!mCanceled) {
@@ -211,7 +205,7 @@
final String estimatedCharset = parameter.estimatedCharset;
final VCardEntryConstructor constructor =
- new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
+ new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
final VCardEntryCommitter committer = mCommitterGenerator.generate(mResolver);
constructor.addEntryHandler(committer);
constructor.addEntryHandler(mNotifier);
@@ -250,7 +244,7 @@
private void doFinishNotification(Uri createdUri) {
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_download_done;
- final String title = mService.getString(R.string.reading_vcard_finished_title);
+ final String title = mService.getString(R.string.importing_vcard_finished_title);
final Intent intent;
if (createdUri != null) {
@@ -266,7 +260,7 @@
final PendingIntent pendingIntent =
PendingIntent.getActivity(mService, 0, intent, 0);
notification.setLatestEventInfo(mService, title, "", pendingIntent);
- mNotificationManager.notify(ImportVCardService.NOTIFICATION_ID, notification);
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
}
private boolean readOneVCard(Uri uri, int vcardType, String charset,
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
index 42d101b..db70218 100644
--- a/src/com/android/contacts/vcard/ImportProgressNotifier.java
+++ b/src/com/android/contacts/vcard/ImportProgressNotifier.java
@@ -56,7 +56,8 @@
// - 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 String packageName = "com.android.contacts.vcard";
+ // TODO: should not create this everytime?
final RemoteViews remoteViews = new RemoteViews(packageName,
R.layout.status_bar_ongoing_event_progress_bar);
final String title = mContext.getString(R.string.reading_vcard_title);
@@ -87,7 +88,7 @@
notification.contentIntent =
PendingIntent.getActivity(mContext, 0,
new Intent(mContext, ContactsListActivity.class), 0);
- mNotificationManager.notify(ImportVCardService.NOTIFICATION_ID, notification);
+ mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
}
public synchronized void addTotalCount(int additionalCount) {
diff --git a/src/com/android/contacts/vcard/ImportRequest.java b/src/com/android/contacts/vcard/ImportRequest.java
index 841a09c..5d46166 100644
--- a/src/com/android/contacts/vcard/ImportRequest.java
+++ b/src/com/android/contacts/vcard/ImportRequest.java
@@ -21,9 +21,9 @@
import com.android.vcard.VCardSourceDetector;
/**
- * Class representing one request for reading vCard (as a Uri representation).
+ * Class representing one request for importing vCard (given as a Uri).
*
- * Mainly used when {@link ImportVCardActivity} requests {@link ImportVCardService}
+ * Mainly used when {@link ImportVCardActivity} requests {@link VCardService}
* to import some specific Uri.
*
* Note: This object's accepting only One Uri does NOT mean that
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index c7606eb..1ae9d3f 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -32,7 +32,6 @@
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
-import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
@@ -100,9 +99,6 @@
/* package */ final static int VCARD_VERSION_V21 = 1;
/* package */ final static int VCARD_VERSION_V30 = 2;
- // Run on the UI thread. Must not be null except after onDestroy().
- private Handler mHandler = new Handler();
-
private AccountSelectionUtil.AccountSelectedListener mAccountSelectionListener;
private Account mAccount;
@@ -133,7 +129,7 @@
public void doBindService() {
bindService(new Intent(ImportVCardActivity.this,
- ImportVCardService.class), this, Context.BIND_AUTO_CREATE);
+ VCardService.class), this, Context.BIND_AUTO_CREATE);
}
public void setNeedFinish() {
@@ -154,14 +150,14 @@
}
}
- private void sendMessage(final ImportRequest parameter) {
+ private void sendMessage(final ImportRequest request) {
try {
mMessenger.send(Message.obtain(null,
- ImportVCardService.MSG_IMPORT_REQUEST,
- parameter));
+ VCardService.MSG_IMPORT_REQUEST,
+ request));
} catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to import vCard");
- runOnUIThread(new DialogDisplayer(
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_unknown)));
}
}
@@ -255,10 +251,10 @@
/**
* Caches all vCard data into local data directory so that we allow
- * {@link ImportVCardService} to access all the contents in given Uris, some of
+ * {@link VCardService} to access all the contents in given Uris, some of
* which may not be accessible from other components due to permission problem.
* (Activity which gives the Uri may allow only this Activity to access that content,
- * not the ohter components like {@link ImportVCardService}.
+ * not the ohter components like {@link VCardService}.
*
* We also allow the Service to happen to exit during the vCard import procedure.
*/
@@ -333,14 +329,16 @@
// smaller memory than we usually expect.
System.gc();
needFinish = false;
+
+ // TODO: call this from connection object.
unbindService(mConnection);
- runOnUIThread(new DialogDisplayer(
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_low_memory_during_import)));
} catch (IOException e) {
Log.e(LOG_TAG, e.getMessage());
needFinish = false;
unbindService(mConnection);
- runOnUIThread(new DialogDisplayer(
+ runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_io_error)));
} finally {
mWakeLock.release();
@@ -631,14 +629,14 @@
mProgressDialogForScanVCard = null;
if (mGotIOException) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_io_exception));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_io_exception));
} else if (mCanceled) {
finish();
} else {
int size = mAllVCardFileList.size();
final Context context = ImportVCardActivity.this;
if (size == 0) {
- runOnUIThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_vcard_not_found));
} else {
startVCardSelectAndImport();
}
@@ -697,9 +695,9 @@
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));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_import_type));
} else {
- runOnUIThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
+ runOnUiThread(new DialogDisplayer(R.id.dialog_select_one_vcard));
}
}
@@ -732,7 +730,7 @@
}
private void importVCard(final Uri[] uris) {
- runOnUIThread(new Runnable() {
+ runOnUiThread(new Runnable() {
public void run() {
mVCardCacheThread = new VCardCacheThread(uris);
showDialog(R.id.dialog_cache_vcard);
@@ -962,33 +960,6 @@
}
}
- @Override
- protected void onDestroy() {
- // The code assumes the handler runs on the UI thread. If not,
- // clearing the message queue is not enough, one would have to
- // make sure that the handler does not run any callback when
- // this activity isFinishing().
-
- // Callbacks messages have what == 0.
- if (mHandler.hasMessages(0)) {
- mHandler.removeMessages(0);
- }
-
- mHandler = null; // Prevents memory leaks by breaking any circular dependency.
- super.onDestroy();
- }
-
- /**
- * Tries to run a given Runnable object when the UI thread can. Ignore it otherwise
- */
- private void runOnUIThread(Runnable runnable) {
- if (mHandler == null) {
- Log.w(LOG_TAG, "Handler object is null. No dialog is shown.");
- } else {
- mHandler.post(runnable);
- }
- }
-
/**
* 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/vcard/ThreadStarter.java b/src/com/android/contacts/vcard/ThreadStarter.java
new file mode 100644
index 0000000..d7adad6
--- /dev/null
+++ b/src/com/android/contacts/vcard/ThreadStarter.java
@@ -0,0 +1,20 @@
+/*
+ * 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.vcard;
+
+public interface ThreadStarter {
+ public void start();
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportVCardService.java b/src/com/android/contacts/vcard/VCardService.java
similarity index 69%
rename from src/com/android/contacts/vcard/ImportVCardService.java
rename to src/com/android/contacts/vcard/VCardService.java
index 76f597c..58e1333 100644
--- a/src/com/android/contacts/vcard/ImportVCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -29,38 +29,51 @@
/**
* The class responsible for importing vCard from one ore multiple Uris.
*/
-public class ImportVCardService extends Service {
+public class VCardService extends Service {
private final static String LOG_TAG = "ImportVCardService";
/* package */ static final int MSG_IMPORT_REQUEST = 1;
+ /* package */ static final int MSG_EXPORT_REQUEST = 2;
- /* package */ static final int NOTIFICATION_ID = 1000;
+ /* package */ static final int IMPORT_NOTIFICATION_ID = 1000;
+ /* package */ static final int EXPORT_NOTIFICATION_ID = 1001;
/**
* Small vCard file is imported soon, so any meassage saying "vCard import started" is
* not needed. We show the message when the size of vCard is larger than this constant.
*/
- private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
+ private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
public class ImportRequestHandler extends Handler {
- private final ImportRequestProcessor mRequestProcessor =
- new ImportRequestProcessor(ImportVCardService.this);
+ private final ImportProcessor mImportProcessor =
+ new ImportProcessor(VCardService.this);
+ private final ExportProcessor mExportProcessor =
+ new ExportProcessor(VCardService.this);
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_IMPORT_REQUEST: {
final ImportRequest parameter = (ImportRequest)msg.obj;
+ mImportProcessor.pushRequest(parameter);
if (parameter.entryCount > IMPORT_NOTIFICATION_THRESHOLD) {
- Toast.makeText(ImportVCardService.this,
+ Toast.makeText(VCardService.this,
getString(R.string.vcard_importer_start_message),
Toast.LENGTH_LONG).show();
}
- mRequestProcessor.pushRequest(parameter);
break;
}
- default:
+ case MSG_EXPORT_REQUEST: {
+ final ExportRequest parameter = (ExportRequest)msg.obj;
+ mExportProcessor.pushRequest(parameter);
+ Toast.makeText(VCardService.this,
+ getString(R.string.vcard_exporter_start_message),
+ Toast.LENGTH_LONG).show();
+ break;
+ }
+ default: {
Log.e(LOG_TAG, "Unknown request type: " + msg.what);
super.hasMessages(msg.what);
+ }
}
}
}