summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
authorDaisuke Miyakawa <dmiyakawa@google.com>2010-11-18 17:28:02 -0800
committerDaisuke Miyakawa <dmiyakawa@google.com>2010-11-22 10:03:42 -0800
commitab59660a17e896593f2a07c2e1191c2c23e3e353 (patch)
tree14274e8f6be029f1ae45e3472427b5e654b2dbf0 /src/com
parent53e3176296eeaab1caff0712c946bb77c3cd72f3 (diff)
downloadpackages_apps_Contacts-ab59660a17e896593f2a07c2e1191c2c23e3e353.tar.gz
packages_apps_Contacts-ab59660a17e896593f2a07c2e1191c2c23e3e353.tar.bz2
packages_apps_Contacts-ab59660a17e896593f2a07c2e1191c2c23e3e353.zip
Allow users to cancel each import/export.
- add cancel capability for vCard export. - use jobId for Notification id, so that users can cancel each vCard import/export request. Note: As for Notification id, it may conflict with each other when VCardService is shutdown. Minor changes: - add file name to each notification: users can see "xxx.vcf is successfully imported" instead of "vcard is successfully imported" - rename mCancelled to mCanceled. strings.xml has "canceled", so inconsistent inside the app. Ignore the inconsistency between the spell in the app and Future#isCancelled(). Bug: 3215008 Change-Id: I7532e3d1b35a8bbeb694e47077554e36190482ed
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/contacts/vcard/CancelActivity.java149
-rw-r--r--src/com/android/contacts/vcard/CancelImportActivity.java128
-rw-r--r--src/com/android/contacts/vcard/CancelRequest.java (renamed from src/com/android/contacts/vcard/CancelImportRequest.java)18
-rw-r--r--src/com/android/contacts/vcard/ExportProcessor.java77
-rw-r--r--src/com/android/contacts/vcard/ImportProcessor.java89
-rw-r--r--src/com/android/contacts/vcard/ImportProgressNotifier.java66
-rw-r--r--src/com/android/contacts/vcard/ImportRequest.java18
-rw-r--r--src/com/android/contacts/vcard/ImportVCardActivity.java18
-rw-r--r--src/com/android/contacts/vcard/ProcessorBase.java7
-rw-r--r--src/com/android/contacts/vcard/VCardService.java220
10 files changed, 456 insertions, 334 deletions
diff --git a/src/com/android/contacts/vcard/CancelActivity.java b/src/com/android/contacts/vcard/CancelActivity.java
new file mode 100644
index 000000000..5e3906bdd
--- /dev/null
+++ b/src/com/android/contacts/vcard/CancelActivity.java
@@ -0,0 +1,149 @@
+/*
+ * 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 com.android.contacts.R;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * The Activity for canceling vCard import/export.
+ */
+public class CancelActivity extends Activity implements ServiceConnection {
+ private final String LOG_TAG = "VCardCancel";
+
+ /* package */ final static String JOB_ID = "job_id";
+ /* package */ final static String DISPLAY_NAME = "display_name";
+
+ /**
+ * Type of the process to be canceled. Only used for choosing appropriate title/message.
+ * Must be {@link VCardService#TYPE_IMPORT} or {@link VCardService#TYPE_EXPORT}.
+ */
+ /* package */ final static String TYPE = "type";
+
+ private class RequestCancelListener implements DialogInterface.OnClickListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ bindService(new Intent(CancelActivity.this,
+ VCardService.class), CancelActivity.this, Context.BIND_AUTO_CREATE);
+ }
+ }
+
+ private class CancelListener
+ implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+ }
+
+ private final CancelListener mCancelListener = new CancelListener();
+ private int mJobId;
+ private String mDisplayName;
+ private int mType;
+ private Messenger mMessenger;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Uri uri = getIntent().getData();
+ mJobId = Integer.parseInt(uri.getQueryParameter(JOB_ID));
+ mDisplayName = uri.getQueryParameter(DISPLAY_NAME);
+ mType = Integer.parseInt(uri.getQueryParameter(TYPE));
+ showDialog(R.id.dialog_cancel_confirmation);
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id, Bundle bundle) {
+ switch (id) {
+ case R.id.dialog_cancel_confirmation: {
+ final String title;
+ final String message;
+ if (mType == VCardService.TYPE_IMPORT) {
+ title = getString(R.string.cancel_import_confirmation_title);
+ message = getString(R.string.cancel_import_confirmation_message, mDisplayName);
+ } else {
+ title = getString(R.string.cancel_export_confirmation_title);
+ message = getString(R.string.cancel_export_confirmation_message, mDisplayName);
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, new RequestCancelListener())
+ .setOnCancelListener(mCancelListener)
+ .setNegativeButton(android.R.string.cancel, mCancelListener);
+ return builder.create();
+ }
+ case R.id.dialog_cancel_failed:
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this)
+ .setTitle(R.string.cancel_vcard_import_or_export_failed)
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setMessage(getString(R.string.fail_reason_unknown))
+ .setOnCancelListener(mCancelListener)
+ .setPositiveButton(android.R.string.ok, mCancelListener);
+ return builder.create();
+ default:
+ Log.w(LOG_TAG, "Unknown dialog id: " + id);
+ break;
+ }
+ return super.onCreateDialog(id, bundle);
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mMessenger = new Messenger(service);
+
+ boolean callFinish = false;
+ try {
+ final CancelRequest request = new CancelRequest(mJobId, mDisplayName);
+ mMessenger.send(Message.obtain(null, VCardService.MSG_CANCEL_REQUEST, request));
+ callFinish = true;
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
+ showDialog(R.id.dialog_cancel_failed);
+ // finish() should be called from the Dialog
+ } finally {
+ unbindService(this);
+ }
+
+ if (callFinish) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mMessenger = null;
+ }
+}
diff --git a/src/com/android/contacts/vcard/CancelImportActivity.java b/src/com/android/contacts/vcard/CancelImportActivity.java
deleted file mode 100644
index b52ee98a6..000000000
--- a/src/com/android/contacts/vcard/CancelImportActivity.java
+++ /dev/null
@@ -1,128 +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.app.Activity;
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.contacts.R;
-
-/**
- * The Activity for canceling ongoing vCard import.
- *
- * Currently we ignore tha case where there are more than one import requests
- * with a same Uri in the queue.
- */
-public class CancelImportActivity extends Activity {
- private final String LOG_TAG = "VCardImporter";
-
- /* package */ final String EXTRA_TARGET_URI = "extra_target_uri";
-
- private class CustomConnection implements ServiceConnection {
- private Messenger mMessenger;
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mMessenger = new Messenger(service);
-
- try {
- mMessenger.send(Message.obtain(null,
- VCardService.MSG_CANCEL_IMPORT_REQUEST,
- null));
- finish();
- } catch (RemoteException e) {
- Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
- CancelImportActivity.this.showDialog(R.string.fail_reason_unknown);
- } finally {
- CancelImportActivity.this.unbindService(this);
- }
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mMessenger = null;
- }
- }
-
- private class RequestCancelImportListener implements DialogInterface.OnClickListener {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- bindService(new Intent(CancelImportActivity.this,
- VCardService.class), mConnection, Context.BIND_AUTO_CREATE);
- }
- }
-
- private class CancelListener
- implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- @Override
- public void onCancel(DialogInterface dialog) {
- finish();
- }
- }
-
- private final CancelListener mCancelListener = new CancelListener();
- private final CustomConnection mConnection = new CustomConnection();
- // private String mTargetUri;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- showDialog(R.id.dialog_cancel_import_confirmation);
- }
-
- @Override
- protected Dialog onCreateDialog(int resId, Bundle bundle) {
- switch (resId) {
-
- case R.id.dialog_cancel_import_confirmation: {
- return getConfirmationDialog();
- }
- case R.string.fail_reason_unknown:
- final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(getString(R.string.reading_vcard_failed_title))
- .setIcon(android.R.drawable.ic_dialog_alert)
- .setMessage(getString(resId))
- .setOnCancelListener(mCancelListener)
- .setPositiveButton(android.R.string.ok, mCancelListener);
- return builder.create();
- }
- return super.onCreateDialog(resId, bundle);
- }
-
- private Dialog getConfirmationDialog() {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this)
- .setTitle(R.string.cancel_import_confirmation_title)
- .setMessage(R.string.cancel_import_confirmation_message)
- .setPositiveButton(android.R.string.ok, new RequestCancelImportListener())
- .setOnCancelListener(mCancelListener)
- .setNegativeButton(android.R.string.cancel, mCancelListener);
- return builder.create();
- }
-} \ No newline at end of file
diff --git a/src/com/android/contacts/vcard/CancelImportRequest.java b/src/com/android/contacts/vcard/CancelRequest.java
index dd10187f7..85893bac1 100644
--- a/src/com/android/contacts/vcard/CancelImportRequest.java
+++ b/src/com/android/contacts/vcard/CancelRequest.java
@@ -15,14 +15,18 @@
*/
package com.android.contacts.vcard;
-import android.net.Uri;
-
/**
- * Class representing one request for canceling vCard import (given as a Uri).
+ * Class representing one request for canceling vCard import/export.
*/
-public class CancelImportRequest {
- public final Uri uri;
- public CancelImportRequest(Uri uri) {
- this.uri = uri;
+public class CancelRequest {
+ public final int jobId;
+ /**
+ * Name used for showing users some useful info. Typically a file name.
+ * Must not be used to do some actual operations.
+ */
+ public final String displayName;
+ public CancelRequest(int jobId, String displayName) {
+ this.jobId = jobId;
+ this.displayName = displayName;
}
} \ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ExportProcessor.java b/src/com/android/contacts/vcard/ExportProcessor.java
index c54df4948..028bcc754 100644
--- a/src/com/android/contacts/vcard/ExportProcessor.java
+++ b/src/com/android/contacts/vcard/ExportProcessor.java
@@ -48,7 +48,7 @@ public class ExportProcessor extends ProcessorBase {
private final ExportRequest mExportRequest;
private final int mJobId;
- private volatile boolean mCancelled;
+ private volatile boolean mCanceled;
private volatile boolean mDone;
public ExportProcessor(VCardService service, ExportRequest exportRequest, int jobId) {
@@ -62,7 +62,7 @@ public class ExportProcessor extends ProcessorBase {
@Override
public final int getType() {
- return PROCESSOR_TYPE_EXPORT;
+ return VCardService.TYPE_EXPORT;
}
@Override
@@ -70,6 +70,13 @@ public class ExportProcessor extends ProcessorBase {
// ExecutorService ignores RuntimeException, so we need to show it here.
try {
runInternal();
+
+ if (isCancelled()) {
+ doCancelNotification();
+ }
+ } catch (OutOfMemoryError e) {
+ Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
+ throw e;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "RuntimeException thrown during export", e);
throw e;
@@ -86,7 +93,7 @@ public class ExportProcessor extends ProcessorBase {
VCardComposer composer = null;
boolean successful = false;
try {
- if (mCancelled) {
+ if (isCancelled()) {
Log.i(LOG_TAG, "Export request is cancelled before handling the request");
return;
}
@@ -147,7 +154,7 @@ public class ExportProcessor extends ProcessorBase {
int current = 1; // 1-origin
while (!composer.isAfterLast()) {
- if (mCancelled) {
+ if (isCancelled()) {
Log.i(LOG_TAG, "Export request is cancelled during composing vCard");
return;
}
@@ -173,7 +180,9 @@ public class ExportProcessor extends ProcessorBase {
Log.i(LOG_TAG, "Successfully finished exporting vCard " + request.destUri);
successful = true;
- final String title = mService.getString(R.string.exporting_vcard_finished_title);
+ final String filename = uri.getLastPathSegment();
+ final String title = mService.getString(R.string.exporting_vcard_finished_title,
+ filename);
doFinishNotification(title, "");
} finally {
if (composer != null) {
@@ -197,58 +206,46 @@ public class ExportProcessor extends ProcessorBase {
}
}
- private void doProgressNotification(Uri uri, int total, int current) {
- final String title = mService.getString(R.string.exporting_contact_list_title);
- final String filename = uri.getLastPathSegment();
+ private void doProgressNotification(Uri uri, int totalCount, int currentCount) {
+ final String displayName = uri.getLastPathSegment();
final String description =
- mService.getString(R.string.exporting_contact_list_message, filename);
-
- final RemoteViews remoteViews = new RemoteViews(mService.getPackageName(),
- R.layout.status_bar_ongoing_event_progress_bar);
- remoteViews.setTextViewText(R.id.status_description, description);
- remoteViews.setProgressBar(R.id.status_progress_bar, total, current, (total == -1));
-
- final String percentage = mService.getString(R.string.percentage,
- String.valueOf((current * 100)/total));
- remoteViews.setTextViewText(R.id.status_progress_text, percentage);
- remoteViews.setImageViewResource(R.id.status_icon, android.R.drawable.stat_sys_upload);
-
- final Notification notification = new Notification();
- notification.icon = android.R.drawable.stat_sys_upload;
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
- notification.tickerText = title;
- notification.contentView = remoteViews;
- notification.contentIntent =
- PendingIntent.getActivity(mService, 0,
- new Intent(mService, ContactBrowserActivity.class), 0);
+ mService.getString(R.string.exporting_contact_list_message, displayName);
+ final String tickerText =
+ mService.getString(R.string.exporting_contact_list_title);
+ final Notification notification =
+ VCardService.constructProgressNotification(mService, VCardService.TYPE_EXPORT,
+ description, tickerText, mJobId, displayName, totalCount, currentCount);
+ mNotificationManager.notify(mJobId, notification);
+ }
- mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ private void doCancelNotification() {
+ final String description = mService.getString(R.string.exporting_vcard_canceled_title,
+ mExportRequest.destUri.getLastPathSegment());
+ final Notification notification =
+ VCardService.constructCancelNotification(mService, description);
+ mNotificationManager.notify(mJobId, 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.flags |= Notification.FLAG_AUTO_CANCEL;
- notification.setLatestEventInfo(mService, title, message, null);
+ private void doFinishNotification(final String title, final String description) {
final Intent intent = new Intent(mService, ContactBrowserActivity.class);
- notification.contentIntent =
- PendingIntent.getActivity(mService, 0, intent, 0);
- mNotificationManager.notify(VCardService.EXPORT_NOTIFICATION_ID, notification);
+ final Notification notification =
+ VCardService.constructFinishNotification(mService, description, intent);
+ mNotificationManager.notify(mJobId, notification);
}
@Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
Log.i(LOG_TAG, "received cancel request");
- if (mDone || mCancelled) {
+ if (mDone || mCanceled) {
return false;
}
- mCancelled = true;
+ mCanceled = true;
return true;
}
@Override
public synchronized boolean isCancelled() {
- return mCancelled;
+ return mCanceled;
}
@Override
diff --git a/src/com/android/contacts/vcard/ImportProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
index 049d0a906..5b5ae7992 100644
--- a/src/com/android/contacts/vcard/ImportProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -38,6 +38,7 @@ import android.content.Intent;
import android.net.Uri;
import android.provider.ContactsContract.RawContacts;
import android.util.Log;
+import android.widget.RemoteViews;
import java.io.IOException;
import java.io.InputStream;
@@ -57,30 +58,32 @@ public class ImportProcessor extends ProcessorBase {
private final ImportRequest mImportRequest;
private final int mJobId;
- private final ImportProgressNotifier mNotifier = new ImportProgressNotifier();
+ private final ImportProgressNotifier mNotifier;
// TODO: remove and show appropriate message instead.
private final List<Uri> mFailedUris = new ArrayList<Uri>();
private VCardParser mVCardParser;
- private volatile boolean mCancelled;
+ private volatile boolean mCanceled;
private volatile boolean mDone;
public ImportProcessor(final VCardService service, final ImportRequest request,
- int jobId) {
+ final int jobId) {
mService = service;
mResolver = mService.getContentResolver();
mNotificationManager = (NotificationManager)
mService.getSystemService(Context.NOTIFICATION_SERVICE);
- mNotifier.init(mService, mNotificationManager);
+
mImportRequest = request;
mJobId = jobId;
+ mNotifier = new ImportProgressNotifier(service, mNotificationManager, jobId,
+ request.originalUri.getLastPathSegment());
}
@Override
public final int getType() {
- return PROCESSOR_TYPE_IMPORT;
+ return VCardService.TYPE_IMPORT;
}
@Override
@@ -88,6 +91,13 @@ public class ImportProcessor extends ProcessorBase {
// ExecutorService ignores RuntimeException, so we need to show it here.
try {
runInternal();
+
+ if (isCancelled()) {
+ doCancelNotification();
+ }
+ } catch (OutOfMemoryError e) {
+ Log.e(LOG_TAG, "OutOfMemoryError thrown during import", e);
+ throw e;
} catch (RuntimeException e) {
Log.e(LOG_TAG, "RuntimeException thrown during import", e);
throw e;
@@ -101,7 +111,7 @@ public class ImportProcessor extends ProcessorBase {
private void runInternal() {
Log.i(LOG_TAG, String.format("vCard import (id: %d) has started.", mJobId));
final ImportRequest request = mImportRequest;
- if (mCancelled) {
+ if (isCancelled()) {
Log.i(LOG_TAG, "Canceled before actually handling parameter (" + request.uri + ")");
return;
}
@@ -145,18 +155,19 @@ public class ImportProcessor extends ProcessorBase {
// value
if (isCancelled()) {
Log.i(LOG_TAG, "vCard import has been canceled (uri: " + uri + ")");
+ // Cancel notification will be done outside this method.
} else {
Log.i(LOG_TAG, "Successfully finished importing one vCard file: " + uri);
- }
- List<Uri> uris = committer.getCreatedUris();
- if (uris != null && uris.size() > 0) {
- doFinishNotification(uris.get(0));
- } else {
- // Not critical, but suspicious.
- Log.w(LOG_TAG,
- "Created Uris is null or 0 length " +
- "though the creation itself is successful.");
- doFinishNotification(null);
+ List<Uri> uris = committer.getCreatedUris();
+ if (uris != null && uris.size() > 0) {
+ doFinishNotification(uris.get(0));
+ } else {
+ // Not critical, but suspicious.
+ Log.w(LOG_TAG,
+ "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);
@@ -164,32 +175,17 @@ public class ImportProcessor extends ProcessorBase {
}
}
- /*
- private void doErrorNotification(int id) {
- final Notification notification = new Notification();
- notification.icon = android.R.drawable.stat_sys_download_done;
- notification.flags |= Notification.FLAG_AUTO_CANCEL;
- final String title = mService.getString(R.string.reading_vcard_failed_title);
- final PendingIntent intent =
- PendingIntent.getActivity(mService, 0, new Intent(), 0);
- notification.setLatestEventInfo(mService, title, "", intent);
- mNotificationManager.notify(MESSAGE_ID, notification);
+ private void doCancelNotification() {
+ final String description = mService.getString(R.string.importing_vcard_canceled_title,
+ mImportRequest.originalUri.getLastPathSegment());
+ final Notification notification =
+ VCardService.constructCancelNotification(mService, description);
+ mNotificationManager.notify(mJobId, notification);
}
- */
private void doFinishNotification(final Uri createdUri) {
- final Notification notification = new Notification();
- final String title;
- notification.flags |= Notification.FLAG_AUTO_CANCEL;
-
- if (isCancelled()) {
- notification.icon = android.R.drawable.stat_notify_error;
- title = mService.getString(R.string.importing_vcard_canceled_title);
- } else {
- notification.icon = android.R.drawable.stat_sys_download_done;
- title = mService.getString(R.string.importing_vcard_finished_title);
- }
-
+ final String description = mService.getString(R.string.importing_vcard_finished_title,
+ mImportRequest.originalUri.getLastPathSegment());
final Intent intent;
if (createdUri != null) {
final long rawContactId = ContentUris.parseId(createdUri);
@@ -200,10 +196,9 @@ public class ImportProcessor extends ProcessorBase {
} else {
intent = null;
}
-
- notification.setLatestEventInfo(mService, title, "",
- PendingIntent.getActivity(mService, 0, intent, 0));
- mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
+ final Notification notification =
+ VCardService.constructFinishNotification(mService, description, intent);
+ mNotificationManager.notify(mJobId, notification);
}
private boolean readOneVCard(Uri uri, int vcardType, String charset,
@@ -231,7 +226,7 @@ public class ImportProcessor extends ProcessorBase {
mVCardParser = (vcardVersion == ImportVCardActivity.VCARD_VERSION_V30 ?
new VCardParser_V30(vcardType) :
new VCardParser_V21(vcardType));
- if (mCancelled) {
+ if (isCancelled()) {
Log.i(LOG_TAG, "ImportProcessor already recieves cancel request, so " +
"send cancel request to vCard parser too.");
mVCardParser.cancel();
@@ -278,10 +273,10 @@ public class ImportProcessor extends ProcessorBase {
@Override
public synchronized boolean cancel(boolean mayInterruptIfRunning) {
Log.i(LOG_TAG, "ImportProcessor received cancel request");
- if (mDone || mCancelled) {
+ if (mDone || mCanceled) {
return false;
}
- mCancelled = true;
+ mCanceled = true;
synchronized (this) {
if (mVCardParser != null) {
mVCardParser.cancel();
@@ -292,7 +287,7 @@ public class ImportProcessor extends ProcessorBase {
@Override
public synchronized boolean isCancelled() {
- return mCancelled;
+ return mCanceled;
}
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
index 6a24bc0e3..d6d0f3f32 100644
--- a/src/com/android/contacts/vcard/ImportProgressNotifier.java
+++ b/src/com/android/contacts/vcard/ImportProgressNotifier.java
@@ -21,27 +21,30 @@ import com.android.vcard.VCardEntryHandler;
import android.app.Notification;
import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.Context;
-import android.content.Intent;
-import android.widget.RemoteViews;
/**
- * {@link VCardEntryHandler} implementation which lets the system update
- * the current status of vCard import.
+ * {@link VCardEntryHandler} implementation letting the system update the current status of
+ * vCard import.
*/
public class ImportProgressNotifier implements VCardEntryHandler {
private static final String LOG_TAG = "VCardImport";
- private Context mContext;
- private NotificationManager mNotificationManager;
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+ private final int mJobId;
+ private final String mDisplayName;
private int mCurrentCount;
private int mTotalCount;
- public void init(Context context, NotificationManager notificationManager) {
+ public ImportProgressNotifier(
+ Context context, NotificationManager notificationManager,
+ int jobId, String displayName) {
mContext = context;
mNotificationManager = notificationManager;
+ mJobId = jobId;
+ mDisplayName = displayName;
}
public void onStart() {
@@ -53,12 +56,6 @@ public class ImportProgressNotifier implements VCardEntryHandler {
return;
}
- // We don't use onStart() 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 totalCountString;
synchronized (this) {
totalCountString = String.valueOf(mTotalCount);
@@ -68,52 +65,19 @@ public class ImportProgressNotifier implements VCardEntryHandler {
String.valueOf(mCurrentCount),
totalCountString,
contactStruct.getDisplayName());
-
-
- final Context context = mContext.getApplicationContext();
-
final String description = mContext.getString(R.string.importing_vcard_description,
contactStruct.getDisplayName());
- final RemoteViews remoteViews =
- new RemoteViews(context.getPackageName(),
- R.layout.status_bar_ongoing_event_progress_bar);
- remoteViews.setTextViewText(R.id.status_description, description);
- remoteViews.setProgressBar(R.id.status_progress_bar, mTotalCount, mCurrentCount,
- mTotalCount == -1);
- final String percentage;
- if (mTotalCount > 0) {
- percentage = context.getString(R.string.percentage,
- String.valueOf(mCurrentCount * 100/mTotalCount));
- } else {
- percentage = "";
- }
- remoteViews.setTextViewText(R.id.status_progress_text, percentage);
- remoteViews.setImageViewResource(R.id.status_icon, android.R.drawable.stat_sys_download);
-
- final Notification notification = new Notification();
- notification.icon = android.R.drawable.stat_sys_download;
- notification.tickerText = tickerText;
- notification.contentView = remoteViews;
- notification.flags |= Notification.FLAG_ONGOING_EVENT;
-
- final PendingIntent pendingIntent =
- PendingIntent.getActivity(context, 0,
- new Intent(context, CancelImportActivity.class),
- PendingIntent.FLAG_UPDATE_CURRENT);
- notification.contentIntent = pendingIntent;
- // notification.setLatestEventInfo(context, title, description, pendingIntent);
- mNotificationManager.notify(VCardService.IMPORT_NOTIFICATION_ID, notification);
+ final Notification notification = VCardService.constructProgressNotification(
+ mContext.getApplicationContext(), VCardService.TYPE_IMPORT, description, tickerText,
+ mJobId, mDisplayName, mTotalCount, mCurrentCount);
+ mNotificationManager.notify(mJobId, notification);
}
public synchronized void addTotalCount(int additionalCount) {
mTotalCount += additionalCount;
}
- public synchronized void resetTotalCount() {
- mTotalCount = 0;
- }
-
public void onEnd() {
}
} \ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportRequest.java b/src/com/android/contacts/vcard/ImportRequest.java
index 5d4616619..e8b56068b 100644
--- a/src/com/android/contacts/vcard/ImportRequest.java
+++ b/src/com/android/contacts/vcard/ImportRequest.java
@@ -35,7 +35,22 @@ public class ImportRequest {
* Can be null (typically when there's no Account available in the system).
*/
public final Account account;
+ /**
+ * Uri to be imported. May have different content than originally given from users, so
+ * when displaying user-friendly information (e.g. "importing xxx.vcf"), use
+ * {@link #originalUri} instead.
+ */
public final Uri uri;
+
+ /**
+ * Original uri given from users.
+ * Useful when showing user-friendly information ("importing xxx.vcf"), as
+ * {@link #uri} may have different name than the original (like "import_tmp_1.vcf").
+ *
+ * This variable must not be used for doing actual processing like re-import, as the app
+ * may not have right permission to do so.
+ */
+ public final Uri originalUri;
/**
* Can be {@link VCardSourceDetector#PARSE_TYPE_UNKNOWN}.
*/
@@ -74,10 +89,11 @@ public class ImportRequest {
*/
public final int entryCount;
public ImportRequest(Account account,
- Uri uri, int estimatedType, String estimatedCharset,
+ Uri uri, Uri originalUri, int estimatedType, String estimatedCharset,
int vcardVersion, int entryCount) {
this.account = account;
this.uri = uri;
+ this.originalUri = originalUri;
this.estimatedVCardType = estimatedType;
this.estimatedCharset = estimatedCharset;
this.vcardVersion = vcardVersion;
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 7a72ca213..948b46562 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -284,7 +284,8 @@ public class ImportVCardActivity extends Activity {
Log.w(LOG_TAG, "destUri is null");
break;
}
- final ImportRequest parameter = constructImportRequest(localDataUri);
+ final ImportRequest parameter = constructImportRequest(
+ localDataUri, sourceUri);
if (mCanceled) {
Log.i(LOG_TAG, "vCard cache operation is canceled.");
return;
@@ -359,13 +360,17 @@ public class ImportVCardActivity extends Activity {
}
/**
- * Reads the Uri (possibly multiple times) and constructs {@link ImportRequest} from
+ * Reads localDataUri (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.
+ * @arg localDataUri Uri actually used for the import. Should be stored in
+ * app local storage, as we cannot guarantee other types of Uris can be read
+ * multiple times. This variable populates {@link ImportRequest#uri}.
+ * @arg originalUri Uri requested to be imported. Used mainly for displaying
+ * information. This variable populates {@link ImportRequest#originalUri}.
*/
- private ImportRequest constructImportRequest(final Uri localDataUri) {
+ private ImportRequest constructImportRequest(
+ final Uri localDataUri, final Uri originalUri) {
final ContentResolver resolver = ImportVCardActivity.this.getContentResolver();
VCardEntryCounter counter = null;
VCardSourceDetector detector = null;
@@ -419,7 +424,8 @@ public class ImportVCardActivity extends Activity {
Log.e(LOG_TAG, "IOException during constructing ImportRequest", e);
return null;
}
- return new ImportRequest(mAccount, localDataUri,
+ return new ImportRequest(mAccount,
+ localDataUri, originalUri,
detector.getEstimatedType(),
detector.getEstimatedCharset(),
vcardVersion, counter.getCount());
diff --git a/src/com/android/contacts/vcard/ProcessorBase.java b/src/com/android/contacts/vcard/ProcessorBase.java
index 17ac6d3e3..833090d91 100644
--- a/src/com/android/contacts/vcard/ProcessorBase.java
+++ b/src/com/android/contacts/vcard/ProcessorBase.java
@@ -33,12 +33,9 @@ import java.util.concurrent.TimeUnit;
*/
public abstract class ProcessorBase implements RunnableFuture<Object> {
- public static final int PROCESSOR_TYPE_IMPORT = 1;
- public static final int PROCESSOR_TYPE_EXPORT = 2;
-
/**
- * @return the type of the processor. Must be {@link #PROCESSOR_TYPE_IMPORT} or
- * {@link #PROCESSOR_TYPE_EXPORT}.
+ * @return the type of the processor. Must be {@link VCardService#TYPE_IMPORT} or
+ * {@link VCardService#TYPE_EXPORT}.
*/
public abstract int getType();
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 367634cb5..cad9fc6dc 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -17,13 +17,19 @@ package com.android.contacts.vcard;
import com.android.contacts.R;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.util.Log;
+import android.widget.RemoteViews;
import android.widget.Toast;
import java.util.HashMap;
@@ -46,10 +52,14 @@ public class VCardService extends Service {
/* 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_CANCEL_REQUEST = 3;
- /* package */ static final int IMPORT_NOTIFICATION_ID = 1000;
- /* package */ static final int EXPORT_NOTIFICATION_ID = 1001;
+ /**
+ * Specifies the type of operation. Used when constructing a {@link Notification}, canceling
+ * some operation, etc.
+ */
+ /* package */ static final int TYPE_IMPORT = 1;
+ /* package */ static final int TYPE_EXPORT = 2;
/* package */ static final String CACHE_FILE_PREFIX = "import_tmp_";
@@ -65,8 +75,8 @@ public class VCardService extends Service {
handleExportRequest((ExportRequest)msg.obj);
break;
}
- case MSG_CANCEL_IMPORT_REQUEST: {
- handleCancelAllImportRequest();
+ case MSG_CANCEL_REQUEST: {
+ handleCancelRequest((CancelRequest)msg.obj);
break;
}
// TODO: add cancel capability for export..
@@ -78,6 +88,8 @@ public class VCardService extends Service {
}
});
+ private NotificationManager mNotificationManager;
+
// Should be single thread, as we don't want to simultaneously handle import and export
// requests.
private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
@@ -90,6 +102,12 @@ public class VCardService extends Service {
new HashMap<Integer, ProcessorBase>();
@Override
+ public void onCreate() {
+ super.onCreate();
+ mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
+ }
+
+ @Override
public int onStartCommand(Intent intent, int flags, int id) {
return START_STICKY;
}
@@ -108,66 +126,74 @@ public class VCardService extends Service {
}
private synchronized void handleImportRequest(ImportRequest request) {
- tryExecute(new ImportProcessor(this, request, mCurrentJobId),
- R.string.vcard_import_will_start_message,
- R.string.vcard_import_request_rejected_message);
+ if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) {
+ final String displayName = request.originalUri.getLastPathSegment();
+ final String message = getString(R.string.vcard_import_will_start_message,
+ displayName);
+ // 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, message, Toast.LENGTH_LONG).show();
+
+ final Notification notification =
+ constructProgressNotification(
+ this, TYPE_IMPORT, message, message, mCurrentJobId,
+ displayName, -1, 0);
+ mNotificationManager.notify(mCurrentJobId, notification);
+ mCurrentJobId++;
+ } else {
+ // 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();
+ }
}
private synchronized void handleExportRequest(ExportRequest request) {
- tryExecute(new ExportProcessor(this, request, mCurrentJobId),
- R.string.vcard_export_will_start_message,
- R.string.vcard_export_request_rejected_message);
+ if (tryExecute(new ExportProcessor(this, request, mCurrentJobId))) {
+ final String displayName = request.destUri.getLastPathSegment();
+ final String message = getString(R.string.vcard_export_will_start_message,
+ displayName);
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ final Notification notification =
+ constructProgressNotification(this, TYPE_EXPORT, message, message,
+ mCurrentJobId, displayName, -1, 0);
+ mNotificationManager.notify(mCurrentJobId, notification);
+ mCurrentJobId++;
+ } else {
+ Toast.makeText(this, getString(R.string.vcard_export_request_rejected_message),
+ Toast.LENGTH_LONG).show();
+ }
}
/**
- * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor and
- * shows appropriate Toast using given resource ids.
- * Updates relevant instances when successful.
+ * Tries to call {@link ExecutorService#execute(Runnable)} toward a given processor.
+ * @return true when successful.
*/
- private synchronized void tryExecute(ProcessorBase processor,
- int successfulMessageId, int rejectedMessageId) {
+ private synchronized boolean tryExecute(ProcessorBase processor) {
try {
mExecutorService.execute(processor);
mRunningJobMap.put(mCurrentJobId, processor);
- 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(successfulMessageId), Toast.LENGTH_LONG).show();
+ return true;
} catch (RejectedExecutionException e) {
Log.w(LOG_TAG, "Failed to excetute a job.", 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(rejectedMessageId), Toast.LENGTH_LONG).show();
+ return false;
}
}
- private void handleCancelAllImportRequest() {
- Log.i(LOG_TAG, "Received cancel import request.");
- cancelAllImportRequests();
- }
+ private void handleCancelRequest(CancelRequest request) {
+ final int jobId = request.jobId;
+ Log.i(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId));
+ final ProcessorBase processor = mRunningJobMap.remove(jobId);
- private synchronized void cancelAllImportRequests() {
- for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
- final ProcessorBase processor = entry.getValue();
- if (processor.getType() == ProcessorBase.PROCESSOR_TYPE_IMPORT) {
- final int jobId = entry.getKey();
- processor.cancel(true);
- mRunningJobMap.remove(jobId);
- Log.i(LOG_TAG, String.format("Canceling job %d", jobId));
- }
- }
- stopServiceWhenNoJob();
- }
-
- private synchronized void cancelAllExportRequests() {
- for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
- final ProcessorBase processor = entry.getValue();
- if (processor.getType() == ProcessorBase.PROCESSOR_TYPE_EXPORT) {
- final int jobId = entry.getKey();
- processor.cancel(true);
- mRunningJobMap.remove(jobId);
- Log.i(LOG_TAG, String.format("Canceling job %d", jobId));
- }
+ if (processor != null) {
+ processor.cancel(true);
+ final String description = processor.getType() == TYPE_IMPORT ?
+ getString(R.string.importing_vcard_canceled_title, request.displayName) :
+ getString(R.string.exporting_vcard_canceled_title, request.displayName);
+ final Notification notification = constructCancelNotification(this, description);
+ mNotificationManager.notify(jobId, notification);
+ } else {
+ Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
}
stopServiceWhenNoJob();
}
@@ -241,4 +267,100 @@ public class VCardService extends Service {
}
}
}
+
+ /**
+ * Constructs a {@link Notification} showing the current status of import/export.
+ * Users can cancel the process with the Notification.
+ *
+ * @param context
+ * @param type import/export
+ * @param description Content of the Notification.
+ * @param tickerText
+ * @param jobId
+ * @param displayName Name to be shown to the Notification (e.g. "finished importing XXXX").
+ * Typycally a file name.
+ * @param totalCount The number of vCard entries to be imported. Used to show progress bar.
+ * -1 lets the system show the progress bar with "indeterminate" state.
+ * @param currentCount The index of current vCard. Used to show progress bar.
+ */
+ /* package */ static Notification constructProgressNotification(
+ Context context, int type, String description, String tickerText,
+ int jobId, String displayName, int totalCount, int currentCount) {
+ final RemoteViews remoteViews =
+ new RemoteViews(context.getPackageName(),
+ R.layout.status_bar_ongoing_event_progress_bar);
+ remoteViews.setTextViewText(R.id.status_description, description);
+ remoteViews.setProgressBar(R.id.status_progress_bar, totalCount, currentCount,
+ totalCount == -1);
+ final String percentage;
+ if (totalCount > 0) {
+ percentage = context.getString(R.string.percentage,
+ String.valueOf(currentCount * 100/totalCount));
+ } else {
+ percentage = "";
+ }
+ remoteViews.setTextViewText(R.id.status_progress_text, percentage);
+ final int icon = (type == TYPE_IMPORT ? android.R.drawable.stat_sys_download :
+ android.R.drawable.stat_sys_upload);
+ remoteViews.setImageViewResource(R.id.status_icon, icon);
+
+ final Notification notification = new Notification();
+ notification.icon = icon;
+ notification.tickerText = tickerText;
+ notification.contentView = remoteViews;
+ notification.flags |= Notification.FLAG_ONGOING_EVENT;
+
+ // Note: We cannot use extra values here (like setIntExtra()), as PendingIntent doesn't
+ // preserve them across multiple Notifications. PendingIntent preserves the first extras
+ // (when flag is not set), or update them when PendingIntent#getActivity() is called
+ // (See PendingIntent#FLAG_UPDATE_CURRENT). In either case, we cannot preserve extras as we
+ // expect (for each vCard import/export request).
+ //
+ // We use query parameter in Uri instead.
+ // Scheme and Authority is arbitorary, assuming CancelActivity never refers them.
+ final Intent intent = new Intent(context, CancelActivity.class);
+ final Uri uri = (new Uri.Builder())
+ .scheme("invalidscheme")
+ .authority("invalidauthority")
+ .appendQueryParameter(CancelActivity.JOB_ID, String.valueOf(jobId))
+ .appendQueryParameter(CancelActivity.DISPLAY_NAME, displayName)
+ .appendQueryParameter(CancelActivity.TYPE, String.valueOf(type)).build();
+ intent.setData(uri);
+
+ notification.contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
+ return notification;
+ }
+
+ /**
+ * Constructs a Notification telling users the process is canceled.
+ *
+ * @param context
+ * @param description Content of the Notification
+ */
+ /* package */ static Notification constructCancelNotification(
+ Context context, String description) {
+ final Notification notification = new Notification();
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.icon = android.R.drawable.stat_notify_error;
+ notification.setLatestEventInfo(context, description, description,
+ PendingIntent.getActivity(context, 0, null, 0));
+ return notification;
+ }
+
+ /**
+ * Constructs a Notification telling users the process is finished.
+ *
+ * @param context
+ * @param description Content of the Notification
+ * @param intent Intent to be launched when the Notification is clicked. Can be null.
+ */
+ /* package */ static Notification constructFinishNotification(
+ Context context, String description, Intent intent) {
+ final Notification notification = new Notification();
+ notification.flags |= Notification.FLAG_AUTO_CANCEL;
+ notification.icon = android.R.drawable.stat_sys_download_done;
+ notification.setLatestEventInfo(context, description, description,
+ PendingIntent.getActivity(context, 0, intent, 0));
+ return notification;
+ }
}