Merge "Fixing twitter profile display"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 07ea96c..7437427 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -806,11 +806,17 @@
[CHAR LIMIT=40] -->
<string name="importing_vcard_canceled_title">Reading vCard data was canceled</string>
- <!-- The message shown when vCard importer started running. -->
- <string name="vcard_importer_start_message">vCard importer started.</string>
+ <!-- The message shown when vCard import request is accepted. The system may start that work soon, or do it later
+ when there are already other import/export requests. [CHAR LIMIT=30] -->
+ <string name="vcard_import_will_start_message">vCard importer will start shortly.</string>
+ <!-- The message shown when a given vCard import request is rejected by the system. [CHAR LIMIT=NONE] -->
+ <string name="vcard_import_request_rejected_message">vCard import request is rejected. Please try later.</string>
+ <!-- The message shown when vCard export request is accepted. The system may start that work soon, or do it later
+ when there are already other import/export requests. [CHAR LIMIT=30] -->
+ <string name="vcard_export_will_start_message">vCard exporter will start shortly.</string>
+ <!-- The message shown when a given vCard export request is rejected by the system. [CHAR LIMIT=NONE] -->
+ <string name="vcard_export_request_rejected_message">vCard export request is rejected. Please try later.</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/export. -->
<string name="percentage">%s%%</string>
@@ -844,9 +850,6 @@
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>
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
index 2532cb2..4634b40 100644
--- a/src/com/android/contacts/vcard/ExportProcessor.java
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -15,6 +15,11 @@
*/
package com.android.contacts.vcard;
+import com.android.contacts.R;
+import com.android.contacts.activities.ContactBrowserActivity;
+import com.android.vcard.VCardComposer;
+import com.android.vcard.VCardConfig;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -26,126 +31,78 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.contacts.R;
-import com.android.contacts.activities.ContactBrowserActivity;
-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";
+/**
+ * Class for processing one export request from a user. Dropped after exporting requested Uri(s).
+ * {@link VCardService} will create another object when there is another export request.
+ */
+public class ExportProcessor implements Runnable {
+ private static final String LOG_TAG = "VCardExport";
- private final Context mContext;
+ private final VCardService mService;
private ContentResolver mResolver;
private NotificationManager mNotificationManager;
- boolean mCanceled;
+ private volatile boolean mCanceled;
- boolean mReadyForRequest;
- private final Queue<ExportRequest> mPendingRequests =
- new LinkedList<ExportRequest>();
+ private ExportRequest mExportRequest;
+ private int mJobId;
- public ExportProcessor(Context context) {
- mContext = context;
+ public ExportProcessor(VCardService service, ExportRequest exportRequest, int jobId) {
+ mService = service;
+ mResolver = service.getContentResolver();
+ mNotificationManager =
+ (NotificationManager)mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ mExportRequest = exportRequest;
+ mJobId = jobId;
}
- /* 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 = mContext.getContentResolver();
- mNotificationManager =
- (NotificationManager)mContext.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.");
- }
-
+ @Override
+ public void run() {
+ // ExecutorService ignores RuntimeException, so we need to show it here.
try {
- while (!mCanceled) {
- final ExportRequest parameter;
- synchronized (this) {
- if (mPendingRequests.size() == 0) {
- mReadyForRequest = false;
- break;
- } else {
- parameter = mPendingRequests.poll();
- }
- } // synchronized (this)
- handleOneRequest(parameter);
- }
-
- doFinishNotification(mContext.getString(R.string.exporting_vcard_finished_title),
- "");
- } finally {
- // Not thread safe. Just in case.
- // TODO: verify this works fine.
- mReadyForRequest = false;
+ runInternal();
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "RuntimeException thrown during export", e);
+ throw e;
}
}
- /* package */ void handleOneRequest(ExportRequest request) {
- boolean shouldCallFinish = true;
+ private void runInternal() {
+ Log.i(LOG_TAG, String.format("vCard export (id: %d) has started.", mJobId));
+ final ExportRequest request = mExportRequest;
VCardComposer composer = null;
+ boolean successful = false;
try {
final Uri uri = request.destUri;
final OutputStream outputStream;
try {
outputStream = mResolver.openOutputStream(uri);
} catch (FileNotFoundException e) {
+ Log.w(LOG_TAG, "FileNotFoundException thrown", e);
// Need concise title.
final String errorReason =
- mContext.getString(R.string.fail_reason_could_not_open_file,
+ mService.getString(R.string.fail_reason_could_not_open_file,
uri, e.getMessage());
- shouldCallFinish = false;
+ Log.i(LOG_TAG, "failed to export (could not open output stream)");
doFinishNotification(errorReason, "");
return;
}
+
final String exportType = request.exportType;
final int vcardType;
if (TextUtils.isEmpty(exportType)) {
vcardType = VCardConfig.getVCardTypeFromString(
- mContext.getString(R.string.config_export_vcard_type));
+ mService.getString(R.string.config_export_vcard_type));
} else {
vcardType = VCardConfig.getVCardTypeFromString(exportType);
}
- composer = new VCardComposer(mContext, vcardType, true);
+ composer = new VCardComposer(mService, vcardType, true);
// for test
// int vcardType = (VCardConfig.VCARD_TYPE_V21_GENERIC |
@@ -155,12 +112,13 @@
composer.addHandler(composer.new HandlerForOutputStream(outputStream));
if (!composer.init()) {
+ Log.w(LOG_TAG, "vCard composer init failed");
final String errorReason = composer.getErrorReason();
Log.e(LOG_TAG, "initialization of vCard composer failed: " + errorReason);
final String translatedErrorReason =
translateComposerError(errorReason);
final String title =
- mContext.getString(R.string.fail_reason_could_not_initialize_exporter,
+ mService.getString(R.string.fail_reason_could_not_initialize_exporter,
translatedErrorReason);
doFinishNotification(title, "");
return;
@@ -169,7 +127,7 @@
final int total = composer.getCount();
if (total == 0) {
final String title =
- mContext.getString(R.string.fail_reason_no_exportable_contact);
+ mService.getString(R.string.fail_reason_no_exportable_contact);
doFinishNotification(title, "");
return;
}
@@ -185,7 +143,7 @@
final String translatedErrorReason =
translateComposerError(errorReason);
final String title =
- mContext.getString(R.string.fail_reason_error_occurred_during_export,
+ mService.getString(R.string.fail_reason_error_occurred_during_export,
translatedErrorReason);
doFinishNotification(title, "");
return;
@@ -193,15 +151,21 @@
doProgressNotification(uri, total, current);
current++;
}
+ Log.i(LOG_TAG, "Successfully finished exporting vCard " + request.destUri);
+
+ successful = true;
+ // TODO: Show "successful"
} finally {
if (composer != null) {
composer.terminate();
}
+
+ mService.handleFinishExportNotification(mJobId, successful);
}
}
private String translateComposerError(String errorMessage) {
- final Resources resources = mContext.getResources();
+ 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)) {
@@ -214,11 +178,11 @@
}
private void doProgressNotification(Uri uri, int total, int current) {
- final String title = mContext.getString(R.string.exporting_contact_list_title);
+ final String title = mService.getString(R.string.exporting_contact_list_title);
final String description =
- mContext.getString(R.string.exporting_contact_list_message, uri);
+ mService.getString(R.string.exporting_contact_list_message, uri);
- /* TODO: fix this
+ /* TODO: we should show more informative Notification to users.
final RemoteViews remoteViews = new RemoteViews(mService.getPackageName(),
R.layout.status_bar_ongoing_event_progress_bar);
remoteViews.setTextViewText(R.id.status_description, message);
@@ -244,7 +208,7 @@
description,
when);
- final Context context = mContext.getApplicationContext();
+ final Context context = mService.getApplicationContext();
final PendingIntent pendingIntent =
PendingIntent.getActivity(context, 0,
new Intent(context, ContactBrowserActivity.class),
@@ -258,10 +222,15 @@
final Notification notification = new Notification();
notification.icon = android.R.drawable.stat_sys_upload_done;
notification.flags |= Notification.FLAG_AUTO_CANCEL;
- notification.setLatestEventInfo(mContext, title, message, null);
- final Intent intent = new Intent(mContext, ContactBrowserActivity.class);
+ notification.setLatestEventInfo(mService, title, message, null);
+ final Intent intent = new Intent(mService, ContactBrowserActivity.class);
notification.contentIntent =
- PendingIntent.getActivity(mContext, 0, intent, 0);
+ PendingIntent.getActivity(mService, 0, intent, 0);
mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
}
+
+ public void cancel() {
+ Log.i(LOG_TAG, "received cancel request");
+ mCanceled = true;
+ }
}
diff --git a/src/com/android/contacts/vcard/ExportVCardActivity.java b/src/com/android/contacts/vcard/ExportVCardActivity.java
index fbe6d48..d53d5b2 100644
--- a/src/com/android/contacts/vcard/ExportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ExportVCardActivity.java
@@ -51,7 +51,7 @@
* dialogs stuffs (like how onCreateDialog() is used).
*/
public class ExportVCardActivity extends Activity {
- private static final String LOG_TAG = "ExportVCardActivity";
+ private static final String LOG_TAG = "VCardExport";
// If true, VCardExporter is able to emits files longer than 8.3 format.
private static final boolean ALLOW_LONG_FILE_NAME = false;
@@ -75,8 +75,7 @@
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) {
@@ -174,7 +173,10 @@
public void onClick(DialogInterface dialog, int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
- mConnection.doBindService();
+ // We don't want the service finishes itself just after this connection.
+ startService(new Intent(ExportVCardActivity.this, VCardService.class));
+ bindService(new Intent(ExportVCardActivity.this, VCardService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
final ExportRequest request = new ExportRequest(mDestUri);
diff --git a/src/com/android/contacts/vcard/ImportProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
index 0e6acee..ca7223d 100644
--- a/src/com/android/contacts/vcard/ImportProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -15,25 +15,6 @@
*/
package com.android.contacts.vcard;
-import android.accounts.Account;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.provider.ContactsContract.RawContacts;
-import android.util.Log;
-
import com.android.contacts.R;
import com.android.vcard.VCardEntryCommitter;
import com.android.vcard.VCardEntryConstructor;
@@ -46,210 +27,73 @@
import com.android.vcard.exception.VCardNotSupportedException;
import com.android.vcard.exception.VCardVersionException;
+import android.accounts.Account;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.ContactsContract.RawContacts;
+import android.util.Log;
+
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Queue;
/**
- * Class for processing incoming import request from {@link ImportVCardActivity}.
- *
- * 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.
+ * Class for processing one import request from a user. Dropped after importing requested Uri(s).
+ * {@link VCardService} will create another object when there is another import request.
*/
-public class ImportProcessor {
- private static final String LOG_TAG = "VCardImporter";
+public class ImportProcessor implements Runnable {
+ private static final String LOG_TAG = "VCardImport";
- private class ImportProcessorConnection implements ServiceConnection {
- private Messenger mMessenger;
- private boolean mBound;
-
- public synchronized void tryBind() {
- mContext.startService(new Intent(mContext, VCardService.class));
- if (!mContext.bindService(new Intent(mContext, VCardService.class),
- this, Context.BIND_AUTO_CREATE)) {
- throw new RuntimeException("Failed to bind to VCardService.");
- }
- mBound = true;
- }
-
- public synchronized void tryUnbind() {
- if (mBound) {
- mContext.unbindService(this);
- // TODO: This is not appropriate. It would be better to send some "stop" request
- // to service and let the service stop itself.
- mContext.stopService(new Intent(mContext, VCardService.class));
- } else {
- // TODO: Not graceful.
- Log.w(LOG_TAG, "unbind() is tried while ServiceConnection is not bound yet");
- }
- mBound = false;
- }
-
- public void sendFinishNotification() {
- try {
- mMessenger.send(Message.obtain(null,
- VCardService.MSG_NOTIFY_IMPORT_FINISHED,
- null));
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- }
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mMessenger = new Messenger(service);
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mMessenger = null;
- }
- }
-
- private final Context mContext;
- private ContentResolver mResolver;
- private NotificationManager mNotificationManager;
+ private final VCardService mService;
+ private final ContentResolver mResolver;
+ private final NotificationManager mNotificationManager;
private final List<Uri> mFailedUris = new ArrayList<Uri>();
- private final List<Uri> mCreatedUris = new ArrayList<Uri>();
private final ImportProgressNotifier mNotifier = new ImportProgressNotifier();
private VCardParser mVCardParser;
- /**
- * 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
- * it does not meaning cancel request is able to immediately stop this object,
- * so we have two variables.
- */
+ private ImportRequest mImportRequest;
+ private int mJobId;
+
private boolean mCanceled;
- /**
- * Meaning that this object is able to accept import requests.
- */
- private boolean mReadyForRequest;
- private final Queue<ImportRequest> mPendingRequests =
- new LinkedList<ImportRequest>();
-
- // For testability.
- /* package */ ThreadStarter mThreadStarter = new ThreadStarter() {
- public void start() {
- final Thread thread = new Thread(new Runnable() {
- public void run() {
- process();
- }
- });
- thread.start();
- }
- };
- /* package */ interface CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver);
- }
- /* package */ class DefaultCommitterGenerator implements CommitterGenerator {
- public VCardEntryCommitter generate(ContentResolver resolver) {
- return new VCardEntryCommitter(mResolver);
- }
+ public ImportProcessor(final VCardService service, final ImportRequest request,
+ int jobId) {
+ mService = service;
+ mResolver = mService.getContentResolver();
+ mNotificationManager = (NotificationManager)
+ mService.getSystemService(Context.NOTIFICATION_SERVICE);
+ mNotifier.init(mService, mNotificationManager);
+ mImportRequest = request;
+ mJobId = jobId;
}
- private CommitterGenerator mCommitterGenerator =
- new DefaultCommitterGenerator();
- public ImportProcessor(final Context context) {
- mContext = context;
- }
-
- /**
- * Checks this object and initialize it if not.
- *
- * This method is needed since {@link VCardService} is not ready when this object is
- * created and we need to delay this initialization, while we want to initialize
- * this object soon in tests.
- */
- /* package */ void ensureInit() {
- if (mResolver == null) {
- // Service object may not ready at the construction time
- // (e.g. ContentResolver may be null).
- mResolver = mContext.getContentResolver();
- mNotificationManager =
- (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
- }
- }
-
- public synchronized void pushRequest(final ImportRequest request) {
- ensureInit();
-
- final boolean needThreadStart;
- if (!mReadyForRequest) {
- mFailedUris.clear();
- mCreatedUris.clear();
-
- mNotifier.init(mContext, mNotificationManager);
- needThreadStart = true;
- } else {
- needThreadStart = false;
- }
- final int count = request.entryCount;
- if (count > 0) {
- mNotifier.addTotalCount(count);
- }
- mPendingRequests.add(request);
- if (needThreadStart) {
- mThreadStarter.start();
- }
-
- mReadyForRequest = true;
- }
-
- /**
- * Starts processing import requests. Never stops until all given requests are
- * processed or some error happens, assuming this method is called from a
- * {@link Thread} object.
- */
- private void process() {
- if (!mReadyForRequest) {
- throw new RuntimeException(
- "process() is called before request being pushed "
- + "or after this object's finishing its processing.");
- }
-
- final ImportProcessorConnection connection = new ImportProcessorConnection();
- connection.tryBind();
+ @Override
+ public void run() {
+ // ExecutorService ignores RuntimeException, so we need to show it here.
try {
- while (!mCanceled) {
- final ImportRequest parameter;
- synchronized (this) {
- if (mPendingRequests.size() == 0) {
- mReadyForRequest = false;
- break;
- } else {
- parameter = mPendingRequests.poll();
- }
- } // synchronized (this)
- handleOneRequest(parameter);
- }
-
- // Currenty we don't have an appropriate way to let users see all URIs imported.
- // Instead, we show one only when there's just one created uri.
- doFinishNotification(mCreatedUris.size() > 0 ? mCreatedUris.get(0) : null);
- connection.sendFinishNotification();
- Log.i(LOG_TAG, "Finished successfully importing all vCard");
- } finally {
- mReadyForRequest = false; // Just in case.
- mNotifier.resetTotalCount();
- connection.tryUnbind();
+ runInternal();
+ } catch (RuntimeException e) {
+ Log.e(LOG_TAG, "RuntimeException thrown during import", e);
+ throw e;
}
}
- /**
- * Would be run inside synchronized block.
- */
- /* package */ boolean handleOneRequest(final ImportRequest request) {
+ private void runInternal() {
+ Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
+ final ImportRequest request = mImportRequest;
if (mCanceled) {
- Log.i(LOG_TAG, "Canceled before actually handling parameter ("
- + request.uri + ")");
- return false;
+ Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
+ return;
}
final int[] possibleVCardVersions;
if (request.vcardVersion == ImportVCardActivity.VCARD_VERSION_AUTO_DETECT) {
@@ -274,29 +118,32 @@
final VCardEntryConstructor constructor =
new VCardEntryConstructor(estimatedVCardType, account, estimatedCharset);
- final VCardEntryCommitter committer = mCommitterGenerator.generate(mResolver);
+ final VCardEntryCommitter committer = new VCardEntryCommitter(mResolver);
constructor.addEntryHandler(committer);
constructor.addEntryHandler(mNotifier);
final boolean successful =
readOneVCard(uri, estimatedVCardType, estimatedCharset,
constructor, possibleVCardVersions);
+
+ mService.handleFinishImportNotification(mJobId, successful);
+
if (successful) {
- Log.i(LOG_TAG, "Successfully finished reading one vCard file finished: " + uri);
+ Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
List<Uri> uris = committer.getCreatedUris();
- if (uris != null) {
- mCreatedUris.addAll(uris);
+ if (uris != null && uris.size() > 0) {
+ doFinishNotification(uris.get(0));
} else {
// Not critical, but suspicious.
Log.w(LOG_TAG,
- "Created Uris is null while the creation itself is successful.");
+ "Created Uris is null or 0 length " +
+ "though the creation itself is successful.");
+ doFinishNotification(null);
}
} else {
Log.w(LOG_TAG, "Failed to read one vCard file: " + uri);
mFailedUris.add(uri);
}
-
- return successful;
}
/*
@@ -319,10 +166,10 @@
if (isCanceled()) {
notification.icon = android.R.drawable.stat_notify_error;
- title = mContext.getString(R.string.importing_vcard_canceled_title);
+ title = mService.getString(R.string.importing_vcard_canceled_title);
} else {
notification.icon = android.R.drawable.stat_sys_download_done;
- title = mContext.getString(R.string.importing_vcard_finished_title);
+ title = mService.getString(R.string.importing_vcard_finished_title);
}
final Intent intent;
@@ -336,8 +183,8 @@
intent = null;
}
- notification.setLatestEventInfo(mContext, title, "",
- PendingIntent.getActivity(mContext, 0, intent, 0));
+ notification.setLatestEventInfo(mService, title, "",
+ PendingIntent.getActivity(mService, 0, intent, 0));
mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
}
@@ -411,10 +258,6 @@
return successful;
}
- public synchronized boolean isReadyForRequest() {
- return mReadyForRequest;
- }
-
public boolean isCanceled() {
return mCanceled;
}
@@ -428,21 +271,4 @@
}
}
}
-
- public List<Uri> getCreatedUrisForTest() {
- return mCreatedUris;
- }
-
- public List<Uri> getFailedUrisForTest() {
- return mFailedUris;
- }
-
- public void injectCommitterGeneratorForTest(
- final CommitterGenerator generator) {
- mCommitterGenerator = generator;
- }
-
- public void initNotifierForTest() {
- mNotifier.init(mContext, mNotificationManager);
- }
}
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 24df063..7a72ca2 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -54,7 +54,6 @@
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
-import android.text.format.DateUtils;
import android.text.style.RelativeSizeSpan;
import android.util.Log;
@@ -71,9 +70,7 @@
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
-import java.util.LinkedList;
import java.util.List;
-import java.util.Queue;
import java.util.Set;
import java.util.Vector;
@@ -87,7 +84,7 @@
* dialogs stuffs (like how onCreateDialog() is used).
*/
public class ImportVCardActivity extends Activity {
- private static final String LOG_TAG = "VCardImporter";
+ private static final String LOG_TAG = "VCardImport";
private static final int SELECT_ACCOUNT = 0;
@@ -113,116 +110,16 @@
private Uri mUri;
private ProgressDialog mProgressDialogForScanVCard;
- private ProgressDialog mProgressDialogForCacheVCard;
+ private ProgressDialog mProgressDialogForCachingVCard;
private List<VCardFile> mAllVCardFileList;
private VCardScanThread mVCardScanThread;
private VCardCacheThread mVCardCacheThread;
+ private ImportRequestConnection mConnection;
private String mErrorMessage;
- private class CustomConnection implements ServiceConnection {
- private Messenger mMessenger;
- /**
- * Stores {@link ImportRequest} objects until actual connection is established.
- */
- private Queue<ImportRequest> mPendingRequests =
- new LinkedList<ImportRequest>();
-
- private boolean mConnected = false;
- private boolean mNeedToCallFinish = false;
- private boolean mDisconnectAndFinishDone = false;
-
- /**
- * Tries to unbind this connection and call {@link ImportVCardActivity#finish()}.
- * When timing is not appropriate, this object remembers this call and
- * call {@link ImportVCardActivity#unbindService(ServiceConnection)} and
- * {@link ImportVCardActivity#finish()} afterwards.
- */
- public void tryDisconnectAndFinish() {
- synchronized (this) {
- if (!mDisconnectAndFinishDone) {
- mNeedToCallFinish = true;
- if (mConnected) {
- // onServiceConnected() is already finished and we need to
- // "manually" call unbindService() here.
- unbindService(this);
- mConnected = false;
- mDisconnectAndFinishDone = true;
- finish();
- } else {
- // If not connected, finish() must be called when connected, as
- // We cannot call finish() now.
- }
- }
- }
- }
-
- public void requestSend(final ImportRequest parameter) {
- synchronized (mPendingRequests) {
- if (mMessenger != null) {
- sendMessage(parameter);
- } else {
- mPendingRequests.add(parameter);
- }
- }
- }
-
- private void sendMessage(final ImportRequest request) {
- try {
- mMessenger.send(Message.obtain(null,
- VCardService.MSG_IMPORT_REQUEST,
- request));
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- runOnUiThread(new DialogDisplayer(
- getString(R.string.fail_reason_unknown)));
- }
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- synchronized (mPendingRequests) {
- mMessenger = new Messenger(service);
-
- // Send pending requests thrown from this Activity before an actual connection
- // is established.
- while (!mPendingRequests.isEmpty()) {
- final ImportRequest parameter = mPendingRequests.poll();
- if (parameter == null) {
- throw new NullPointerException();
- }
- sendMessage(parameter);
- }
- }
-
- synchronized (this) {
- if (!mDisconnectAndFinishDone) {
- mConnected = true;
- if (mNeedToCallFinish) {
- mDisconnectAndFinishDone = true;
- finish();
- }
- }
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- synchronized (mPendingRequests) {
- 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;
- }
- }
-
private static class VCardFile {
private final String mName;
private final String mCanonicalPath;
@@ -275,25 +172,53 @@
private CancelListener mCancelListener = new CancelListener();
+ private class ImportRequestConnection implements ServiceConnection {
+ private Messenger mMessenger;
+
+ public void sendImportRequest(final ImportRequest request) {
+ Log.i(LOG_TAG, String.format("Send an import request (Uri: %s)", request.uri));
+ try {
+ mMessenger.send(Message.obtain(null,
+ VCardService.MSG_IMPORT_REQUEST,
+ request));
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ runOnUiThread(new DialogDisplayer(getString(R.string.fail_reason_unknown)));
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mMessenger = new Messenger(service);
+ Log.i(LOG_TAG,
+ String.format("Connected to VCardService. Kick a vCard cache thread (uri: %s)",
+ Arrays.toString(mVCardCacheThread.getSourceUris())));
+ mVCardCacheThread.start();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(LOG_TAG, "Disconnected from VCardService");
+ }
+ }
+
/**
- * Caches all vCard data into local data directory so that we allow
- * {@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 other components like {@link VCardService}.
+ * Caches given vCard files into a local directory, and sends actual import request to
+ * {@link VCardService}.
*
- * We also allow the Service to happen to exit during the vCard import procedure.
+ * We need to cache given files into local storage. One of reasons is that some data (as Uri)
+ * may have special permissions. Callers may allow only this Activity to access that content,
+ * not what this Activity launched (like {@link VCardService}).
*/
private class VCardCacheThread extends Thread
implements DialogInterface.OnCancelListener {
private boolean mCanceled;
private PowerManager.WakeLock mWakeLock;
private VCardParser mVCardParser;
- private final Uri[] mSourceUris;
+ private final Uri[] mSourceUris; // Given from a caller.
public VCardCacheThread(final Uri[] sourceUris) {
mSourceUris = sourceUris;
- final int length = sourceUris.length;
final Context context = ImportVCardActivity.this;
final PowerManager powerManager =
(PowerManager)context.getSystemService(Context.POWER_SERVICE);
@@ -305,21 +230,27 @@
@Override
public void finalize() {
if (mWakeLock != null && mWakeLock.isHeld()) {
+ Log.w(LOG_TAG, "WakeLock is being held.");
mWakeLock.release();
}
}
@Override
public void run() {
- final Context context = ImportVCardActivity.this;
- final ContentResolver resolver = context.getContentResolver();
- String errorMessage = null;
+ Log.i(LOG_TAG, "vCard cache thread starts running.");
+ if (mConnection == null) {
+ throw new NullPointerException("vCard cache thread must be launched "
+ + "after a service connection is established");
+ }
+
mWakeLock.acquire();
- final CustomConnection connection = new CustomConnection();
- startService(new Intent(ImportVCardActivity.this, VCardService.class));
- bindService(new Intent(ImportVCardActivity.this,
- VCardService.class), connection, Context.BIND_AUTO_CREATE);
try {
+ if (mCanceled == true) {
+ Log.i(LOG_TAG, "vCard cache operation is canceled.");
+ return;
+ }
+
+ final Context context = ImportVCardActivity.this;
// Uris given from caller applications may not be opened twice: consider when
// it is not from local storage (e.g. "file:///...") but from some special
// provider (e.g. "content://...").
@@ -346,31 +277,36 @@
}
final Uri localDataUri = copyTo(sourceUri, filename);
if (mCanceled) {
+ Log.i(LOG_TAG, "vCard cache operation is canceled.");
break;
}
if (localDataUri == null) {
Log.w(LOG_TAG, "destUri is null");
break;
}
- final ImportRequest parameter = constructRequestParameter(localDataUri);
+ final ImportRequest parameter = constructImportRequest(localDataUri);
if (mCanceled) {
+ Log.i(LOG_TAG, "vCard cache operation is canceled.");
return;
}
- connection.requestSend(parameter);
+ mConnection.sendImportRequest(parameter);
}
} catch (OutOfMemoryError e) {
- Log.e(LOG_TAG, "OutOfMemoryError");
+ Log.e(LOG_TAG, "OutOfMemoryError occured during caching vCard");
System.gc();
runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_low_memory_during_import)));
} catch (IOException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "IOException during caching vCard", e);
runOnUiThread(new DialogDisplayer(
getString(R.string.fail_reason_io_error)));
} finally {
+ Log.i(LOG_TAG, "Finished caching vCard.");
mWakeLock.release();
- mProgressDialogForCacheVCard.dismiss();
- connection.tryDisconnectAndFinish();
+ unbindService(mConnection);
+ mProgressDialogForCachingVCard.dismiss();
+ mProgressDialogForCachingVCard = null;
+ finish();
}
}
@@ -423,28 +359,27 @@
}
/**
- * Reads the Uri once (or twice) and constructs {@link ImportRequest} from
+ * Reads the Uri (possibly multiple times) and constructs {@link ImportRequest} from
* its content.
+ *
+ * Uri should be local one, as we cannot guarantee other types of Uris can be read
+ * multiple times.
*/
- private ImportRequest constructRequestParameter(final Uri uri) {
- final ContentResolver resolver =
- ImportVCardActivity.this.getContentResolver();
+ private ImportRequest constructImportRequest(final Uri localDataUri) {
+ final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
VCardInterpreterCollection interpreter = null;
int vcardVersion = VCARD_VERSION_V21;
try {
boolean shouldUseV30 = false;
- InputStream is;
-
- is = resolver.openInputStream(uri);
+ InputStream is = resolver.openInputStream(localDataUri);
mVCardParser = new VCardParser_V21();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
- interpreter =
- new VCardInterpreterCollection(
- Arrays.asList(counter, detector));
+ interpreter = new VCardInterpreterCollection(
+ Arrays.asList(counter, detector));
mVCardParser.parse(is, interpreter);
} catch (VCardVersionException e1) {
try {
@@ -453,14 +388,13 @@
}
shouldUseV30 = true;
- is = resolver.openInputStream(uri);
+ is = resolver.openInputStream(localDataUri);
mVCardParser = new VCardParser_V30();
try {
counter = new VCardEntryCounter();
detector = new VCardSourceDetector();
- interpreter =
- new VCardInterpreterCollection(
- Arrays.asList(counter, detector));
+ interpreter = new VCardInterpreterCollection(
+ Arrays.asList(counter, detector));
mVCardParser.parse(is, interpreter);
} catch (VCardVersionException e2) {
throw new VCardException("vCard with unspported version.");
@@ -479,13 +413,13 @@
Log.w(LOG_TAG, "Nested Exception is found (it may be false-positive).");
// Go through without returning null.
} catch (VCardException e) {
- Log.e(LOG_TAG, e.getMessage());
+ Log.e(LOG_TAG, "VCardException during constructing ImportRequest", e);
return null;
} catch (IOException e) {
- Log.e(LOG_TAG, "IOException was emitted: " + e.getMessage());
+ Log.e(LOG_TAG, "IOException during constructing ImportRequest", e);
return null;
}
- return new ImportRequest(mAccount, uri,
+ return new ImportRequest(mAccount, localDataUri,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
@@ -502,7 +436,9 @@
}
}
+ @Override
public void onCancel(DialogInterface dialog) {
+ Log.i(LOG_TAG, "Cancel request has come. Abort caching vCard.");
cancel();
}
}
@@ -921,17 +857,23 @@
return getVCardFileSelectDialog(false);
}
case R.id.dialog_cache_vcard: {
- if (mProgressDialogForCacheVCard == null) {
+ if (mProgressDialogForCachingVCard == null) {
final String title = getString(R.string.caching_vcard_title);
final String message = getString(R.string.caching_vcard_message);
- mProgressDialogForCacheVCard = new ProgressDialog(this);
- mProgressDialogForCacheVCard.setTitle(title);
- mProgressDialogForCacheVCard.setMessage(message);
- mProgressDialogForCacheVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
- mProgressDialogForCacheVCard.setOnCancelListener(mVCardCacheThread);
- mVCardCacheThread.start();
+ mProgressDialogForCachingVCard = new ProgressDialog(this);
+ mProgressDialogForCachingVCard.setTitle(title);
+ mProgressDialogForCachingVCard.setMessage(message);
+ mProgressDialogForCachingVCard.setProgressStyle(ProgressDialog.STYLE_SPINNER);
+ mProgressDialogForCachingVCard.setOnCancelListener(mVCardCacheThread);
+ mConnection = new ImportRequestConnection();
+
+ Log.i(LOG_TAG, "Bind to VCardService.");
+ // We don't want the service finishes itself just after this connection.
+ startService(new Intent(this, VCardService.class));
+ bindService(new Intent(this, VCardService.class),
+ mConnection, Context.BIND_AUTO_CREATE);
}
- return mProgressDialogForCacheVCard;
+ return mProgressDialogForCachingVCard;
}
case R.id.dialog_io_exception: {
String message = (getString(R.string.scanning_sdcard_failed_message,
@@ -964,31 +906,11 @@
}
@Override
- protected void onSaveInstanceState(Bundle outState) {
- if (mVCardCacheThread != null) {
- final Uri[] uris = mVCardCacheThread.getSourceUris();
- final int length = uris.length;
- final String[] uriStrings = new String[length];
- for (int i = 0; i < length; i++) {
- uriStrings[i] = uris[i].toString();
- }
- outState.putStringArray(CACHED_URIS, uriStrings);
-
- mVCardCacheThread.cancel();
- }
- }
-
- @Override
- protected void onRestoreInstanceState(Bundle inState) {
- final String[] uriStrings = inState.getStringArray(CACHED_URIS);
- if (uriStrings != null && uriStrings.length > 0) {
- final int length = uriStrings.length;
- final Uri[] uris = new Uri[length];
- for (int i = 0; i < length; i++) {
- uris[i] = Uri.parse(uriStrings[i]);
- }
-
- mVCardCacheThread = new VCardCacheThread(uris);
+ protected void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ if (mProgressDialogForCachingVCard != null) {
+ Log.i(LOG_TAG, "Cache thread is still running. Show progress dialog again.");
+ showDialog(R.id.dialog_cache_vcard);
}
}
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 475d465..77dc8bd 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -15,98 +15,63 @@
*/
package com.android.contacts.vcard;
+import com.android.contacts.R;
+
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
-import android.text.format.DateUtils;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.Date;
-
-import com.android.contacts.R;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
/**
- * The class responsible for importing vCard from one ore multiple Uris.
+ * The class responsible for handling vCard import/export requests.
+ *
+ * This Service creates one ImportRequest/ExportRequest object (as Runnable) per request and push
+ * it to {@link ExecutorService} with single thread executor. The executor handles each request
+ * one by one, and notifies users when needed.
*/
+// TODO: Using IntentService looks simpler than using Service + ServiceConnection though this
+// works fine enough. Investigate the feasibility.
public class VCardService extends Service {
- private final static String LOG_TAG = VCardService.class.getSimpleName();
+ private final static String LOG_TAG = "VCardService";
/* package */ static final int MSG_IMPORT_REQUEST = 1;
/* package */ static final int MSG_EXPORT_REQUEST = 2;
/* package */ static final int MSG_CANCEL_IMPORT_REQUEST = 3;
- /* package */ static final int MSG_NOTIFY_IMPORT_FINISHED = 5;
/* package */ static final int IMPORT_NOTIFICATION_ID = 1000;
/* package */ static final int EXPORT_NOTIFICATION_ID = 1001;
/* package */ static final String CACHE_FILE_PREFIX = "import_tmp_";
- public class ImportRequestHandler extends Handler {
- private ImportProcessor mImportProcessor;
- private ExportProcessor mExportProcessor = new ExportProcessor(VCardService.this);
- private boolean mDoDelayedCancel = false;
-
- public ImportRequestHandler() {
- super();
- }
-
+ public class RequestHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_IMPORT_REQUEST: {
- Log.i(LOG_TAG, "Received vCard import request.");
- if (mDoDelayedCancel) {
- Log.i(LOG_TAG, "A cancel request came before import request. " +
- "Refrain current import once.");
- mDoDelayedCancel = false;
- } else {
- final ImportRequest parameter = (ImportRequest)msg.obj;
-
- if (mImportProcessor == null || !mImportProcessor.isReadyForRequest()) {
- mImportProcessor = new ImportProcessor(VCardService.this);
- } else if (mImportProcessor.isCanceled()) {
- Log.i(LOG_TAG,
- "Existing ImporterProcessor is canceled. create another.");
- mImportProcessor = new ImportProcessor(VCardService.this);
- }
-
- mImportProcessor.pushRequest(parameter);
- Toast.makeText(VCardService.this,
- getString(R.string.vcard_importer_start_message),
- Toast.LENGTH_LONG).show();
- }
+ handleImportRequest((ImportRequest)msg.obj);
break;
}
case MSG_EXPORT_REQUEST: {
- Log.i(LOG_TAG, "Received vCard 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();
+ handleExportRequest((ExportRequest)msg.obj);
break;
}
case MSG_CANCEL_IMPORT_REQUEST: {
- Log.i(LOG_TAG, "Received cancel import request.");
- if (mImportProcessor != null) {
- mImportProcessor.cancel();
- } else {
- Log.w(LOG_TAG, "ImportProcessor isn't ready. Delay the cancel request.");
- mDoDelayedCancel = true;
- }
+ handleCancelAllImportRequest();
break;
}
- case MSG_NOTIFY_IMPORT_FINISHED: {
- Log.i(LOG_TAG, "Received vCard import finish notification.");
- break;
- }
+ // TODO: add cancel capability for export..
default: {
Log.w(LOG_TAG, "Received unknown request, ignoring it.");
super.hasMessages(msg.what);
@@ -115,8 +80,17 @@
}
}
- private ImportRequestHandler mHandler = new ImportRequestHandler();
- private Messenger mMessenger = new Messenger(mHandler);
+ private final Handler mHandler = new RequestHandler();
+ private final Messenger mMessenger = new Messenger(mHandler);
+ // Should be single thread, as we don't want to simultaneously handle import and export
+ // requests.
+ private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
+
+ private int mCurrentJobId;
+ private final Map<Integer, ImportProcessor> mRunningJobMapForImport =
+ new HashMap<Integer, ImportProcessor>();
+ private final Map<Integer, ExportProcessor> mRunningJobMapForExport =
+ new HashMap<Integer, ExportProcessor>();
@Override
public int onStartCommand(Intent intent, int flags, int id) {
@@ -130,10 +104,123 @@
@Override
public void onDestroy() {
+ Log.i(LOG_TAG, "VCardService is finishing()");
+ cancelRequestsAndshutdown();
clearCache();
super.onDestroy();
}
+ private synchronized void handleImportRequest(ImportRequest request) {
+ Log.i(LOG_TAG, String.format("Received vCard import request. id: %d", mCurrentJobId));
+ final ImportProcessor importProcessor =
+ new ImportProcessor(this, request, mCurrentJobId);
+ try {
+ mExecutorService.submit(importProcessor);
+ } catch (RejectedExecutionException e) {
+ Log.w(LOG_TAG, "vCard import request is rejected.", e);
+ // TODO: a little unkind to show Toast in this case, which is shown just a moment.
+ // Ideally we should show some persistent something users can notice more easily.
+ Toast.makeText(this, getString(R.string.vcard_import_request_rejected_message),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ mRunningJobMapForImport.put(mCurrentJobId, importProcessor);
+ mCurrentJobId++;
+ // TODO: Ideally we should detect the current status of import/export and show "started"
+ // when we can import right now and show "will start" when we cannot.
+ Toast.makeText(this, getString(R.string.vcard_import_will_start_message),
+ Toast.LENGTH_LONG).show();
+ }
+
+ private synchronized void handleExportRequest(ExportRequest request) {
+ Log.i(LOG_TAG, String.format("Received vCard export request. id: %d", mCurrentJobId));
+ final ExportProcessor exportProcessor =
+ new ExportProcessor(this, request, mCurrentJobId);
+ try {
+ mExecutorService.submit(exportProcessor);
+ } catch (RejectedExecutionException e) {
+ Log.w(LOG_TAG, "vCard export request is rejected.", e);
+ Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
+ Toast.LENGTH_LONG).show();
+ return;
+ }
+ mRunningJobMapForExport.put(mCurrentJobId, exportProcessor);
+ mCurrentJobId++;
+ // See the comment in handleImportRequest()
+ Toast.makeText(this, getString(R.string.vcard_export_will_start_message),
+ Toast.LENGTH_LONG).show();
+ }
+
+ private synchronized void handleCancelAllImportRequest() {
+ Log.i(LOG_TAG, "Received cancel import request.");
+ cancelAllImportRequest();
+ mRunningJobMapForImport.clear();
+ }
+
+ private void cancelAllImportRequest() {
+ for (final Map.Entry<Integer, ImportProcessor> entry :
+ mRunningJobMapForImport.entrySet()) {
+ final int jobId = entry.getKey();
+ final ImportProcessor importProcessor = entry.getValue();
+ importProcessor.cancel();
+ Log.i(LOG_TAG, String.format("Canceling job %d", jobId));
+ }
+ }
+
+ private void cancelAllExportRequest() {
+ for (final Map.Entry<Integer, ExportProcessor> entry :
+ mRunningJobMapForExport.entrySet()) {
+ final int jobId = entry.getKey();
+ final ExportProcessor exportProcessor = entry.getValue();
+ exportProcessor.cancel();
+ Log.i(LOG_TAG, String.format("Canceling job %d", jobId));
+ }
+ }
+
+ /* package */ synchronized void handleFinishImportNotification(
+ int jobId, boolean successful) {
+ Log.i(LOG_TAG, String.format("Received vCard import finish notification (id: %d). "
+ + "Result: %b", jobId, (successful ? "success" : "failure")));
+ if (mRunningJobMapForImport.remove(jobId) == null) {
+ Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
+ }
+ }
+
+ /* package */ synchronized void handleFinishExportNotification(
+ int jobId, boolean successful) {
+ Log.i(LOG_TAG, String.format("Received vCard export finish notification (id: %d). "
+ + "Result: %b", jobId, (successful ? "success" : "failure")));
+ if (mRunningJobMapForExport.remove(jobId) == null) {
+ Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
+ }
+ }
+
+ /**
+ * Cancels all the import/export requests and call {@link ExecutorService#shutdown()}, which
+ * means this Service becomes no longer ready for import/export requests. Mainly used in
+ * onDestroy().
+ */
+ private synchronized void cancelRequestsAndshutdown() {
+ synchronized (this) {
+ if (mRunningJobMapForImport.size() > 0) {
+ Log.i(LOG_TAG,
+ String.format("Cancel existing all import requests (remains: ",
+ mRunningJobMapForImport.size()));
+ cancelAllImportRequest();
+ }
+ if (mRunningJobMapForExport.size() > 0) {
+ Log.i(LOG_TAG,
+ String.format("Cancel existing all import requests (remains: ",
+ mRunningJobMapForExport.size()));
+ cancelAllExportRequest();
+ }
+ mExecutorService.shutdown();
+ }
+ }
+
+ /**
+ * Removes import caches stored locally.
+ */
private void clearCache() {
Log.i(LOG_TAG, "start removing cache files if exist.");
final String[] fileLists = fileList();
diff --git a/tests/src/com/android/contacts/vcard/ImportProcessorTest.java b/tests/src/com/android/contacts/vcard/ImportProcessorTest.java
deleted file mode 100644
index 72245ba..0000000
--- a/tests/src/com/android/contacts/vcard/ImportProcessorTest.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * 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.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-import android.util.Log;
-
-import com.android.contacts.vcard.ImportProcessor.CommitterGenerator;
-import com.android.vcard.VCardEntryCommitter;
-import com.android.vcard.VCardInterpreter;
-import com.android.vcard.VCardSourceDetector;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.List;
-
-public class ImportProcessorTest extends AndroidTestCase {
- private static final String LOG_TAG = "ImportProcessorTest";
- private ImportProcessor mImportProcessor;
-
- private String mCopiedFileName;
-
- // XXX: better way to copy stream?
- private Uri copyToLocal(final String fileName) throws IOException {
- final Context context = getContext();
- // We need to use Context of this unit test runner (not of test to be tested),
- // as only the former knows assets to be copied.
- final Context testContext = getTestContext();
- final ContentResolver resolver = testContext.getContentResolver();
- mCopiedFileName = fileName;
- ReadableByteChannel inputChannel = null;
- WritableByteChannel outputChannel = null;
- Uri destUri;
- try {
- inputChannel = Channels.newChannel(testContext.getAssets().open(fileName));
- destUri = Uri.parse(context.getFileStreamPath(fileName).toURI().toString());
- outputChannel =
- getContext().openFileOutput(fileName,
- Context.MODE_WORLD_WRITEABLE).getChannel();
- final ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
- while (inputChannel.read(buffer) != -1) {
- buffer.flip();
- outputChannel.write(buffer);
- buffer.compact();
- }
- buffer.flip();
- while (buffer.hasRemaining()) {
- outputChannel.write(buffer);
- }
- } finally {
- if (inputChannel != null) {
- try {
- inputChannel.close();
- } catch (IOException e) {
- Log.w(LOG_TAG, "Failed to close inputChannel.");
- }
- }
- if (outputChannel != null) {
- try {
- outputChannel.close();
- } catch(IOException e) {
- Log.w(LOG_TAG, "Failed to close outputChannel");
- }
- }
- }
- return destUri;
- }
-
- @Override
- public void setUp() {
- mImportProcessor = new ImportProcessor(getContext());
- mImportProcessor.ensureInit();
- mCopiedFileName = null;
- }
-
- @Override
- public void tearDown() {
- if (!TextUtils.isEmpty(mCopiedFileName)) {
- getContext().deleteFile(mCopiedFileName);
- mCopiedFileName = null;
- }
- }
-
- /**
- * Confirms {@link ImportProcessor#readOneVCard(android.net.Uri, int, String,
- * com.android.vcard.VCardInterpreter, int[])} successfully handles correct input.
- */
- public void testProcessSimple() throws IOException {
- final Uri uri = copyToLocal("v21_simple.vcf");
- final int vcardType = VCardSourceDetector.PARSE_TYPE_UNKNOWN;
- final String charset = null;
- final VCardInterpreter interpreter = new EmptyVCardInterpreter();
- final int[] versions = new int[] {
- ImportVCardActivity.VCARD_VERSION_V21
- };
-
- assertTrue(mImportProcessor.readOneVCard(
- uri, vcardType, charset, interpreter, versions));
- }
-
- /**
- * Confirms {@link ImportProcessor#handleOneRequest(ImportRequest)} accepts
- * one request and import it.
- */
- public void testHandleOneRequestSimple() throws IOException {
- CommitterGenerator generator = new CommitterGenerator() {
- public VCardEntryCommitter generate(ContentResolver resolver) {
- return new MockVCardEntryCommitter();
- }
- };
- mImportProcessor.injectCommitterGeneratorForTest(generator);
- mImportProcessor.initNotifierForTest();
-
- final ImportRequest request = new ImportRequest(
- null, // account
- copyToLocal("v30_simple.vcf"),
- VCardSourceDetector.PARSE_TYPE_UNKNOWN,
- null, // estimatedCharset
- ImportVCardActivity.VCARD_VERSION_AUTO_DETECT,
- 1);
- assertTrue(mImportProcessor.handleOneRequest(request));
- assertEquals(1, mImportProcessor.getCreatedUrisForTest().size());
- }
-}
-
-/* package */ class EmptyVCardInterpreter implements VCardInterpreter {
- @Override
- public void end() {
- }
- @Override
- public void endEntry() {
- }
- @Override
- public void endProperty() {
- }
- @Override
- public void propertyGroup(String group) {
- }
- @Override
- public void propertyName(String name) {
- }
- @Override
- public void propertyParamType(String type) {
- }
- @Override
- public void propertyParamValue(String value) {
- }
- @Override
- public void propertyValues(List<String> values) {
- }
- @Override
- public void start() {
- }
- @Override
- public void startEntry() {
- }
- @Override
- public void startProperty() {
- }
-}
diff --git a/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java b/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java
deleted file mode 100644
index 4765b38..0000000
--- a/tests/src/com/android/contacts/vcard/MockVCardEntryCommitter.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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;
-
-import com.android.vcard.VCardEntry;
-import com.android.vcard.VCardEntryCommitter;
-
-import java.util.ArrayList;
-
-public class MockVCardEntryCommitter extends VCardEntryCommitter {
-
- private final ArrayList<Uri> mUris = new ArrayList<Uri>();
-
- public MockVCardEntryCommitter() {
- super(null);
- }
-
- /**
- * Exists for forcing super class to do nothing.
- */
- @Override
- public void onStart() {
- }
-
- /**
- * Exists for forcing super class to do nothing.
- */
- @Override
- public void onEnd() {
- }
-
- @Override
- public void onEntryCreated(final VCardEntry vcardEntry) {
- mUris.add(null);
- }
-
- @Override
- public ArrayList<Uri> getCreatedUris() {
- return mUris;
- }
-}
\ No newline at end of file