Add Cancel capability toward vCard import procedure.

Bug: 2801638

Change-Id: Ia563fa4aed48ad01a6fbb29b350a35c46b7085a3
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 57f2c35..992bf5d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -508,6 +508,9 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".vcard.CancelImportActivity"
+            android:theme="@style/BackgroundOnly" />
+
         <activity android:name=".vcard.SelectAccountActivity"
             android:theme="@style/BackgroundOnly" />
 
diff --git a/res/values/ids.xml b/res/values/ids.xml
index 77673e3..b1ed87f 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -20,7 +20,7 @@
     <item type="id" name="dialog_sync_add"/>
     <item type="id" name="dialog_import_export"/>
 
-    <!-- For ImportVCardActivity -->
+    <!-- For vcard.ImportVCardActivity -->
     <item type="id" name="dialog_searching_vcard"/>
     <item type="id" name="dialog_sdcard_not_found"/>
     <item type="id" name="dialog_vcard_not_found"/>
@@ -31,6 +31,9 @@
     <item type="id" name="dialog_io_exception"/>
     <item type="id" name="dialog_error_with_message"/>
 
+    <!-- For vcard.CancelImportActivity -->
+    <item type="id" name="dialog_cancel_import_confirmation"/>
+
     <!-- For ContactDeletionInteraction -->
     <item type="id" name="dialog_delete_contact_confirmation"/>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 43e6489..ecfe5c8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -768,9 +768,12 @@
     <!-- The title shown when reading vCard is canceled (probably by a user) -->
     <string name="reading_vcard_canceled_title">Reading vCard data was canceled</string>
 
-    <!-- The title shown when reading vCard is canceled (probably by a user) -->
+    <!-- The title shown when reading vCard finished -->
     <string name="importing_vcard_finished_title">Finished importing vCard</string>
 
+    <!-- The title shown when importing vCard is canceled (probably by a user) -->
+    <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>
 
@@ -847,6 +850,14 @@
     <!-- Message in progress bar while exporting contact list to a file "(current number) of (total number) contacts" The order of "current number" and "total number" cannot be changed (like "total: (total number), current: (current number)")-->
     <string name="exporting_contact_list_progress"><xliff:g id="current_number">%s</xliff:g> of <xliff:g id="total_number">%s</xliff:g> contacts</string>
 
+    <!-- Title shown in a Dialog confirming a user's cancel request toward existing vCard import. -->
+    <string name="cancel_import_confirmation_title">Canceling import vCard</string>
+
+    <!-- Message shown in a Dialog confirming a user's cancel request toward existing vCard import.
+         The argument is the Uri for the vCard import the user wants to cancel.
+      -->
+    <string name="cancel_import_confirmation_message">Are you sure to cancel importing vCard?</string>
+
     <!-- The string used to describe Contacts as a searchable item within system search settings. -->
     <string name="search_settings_description">Names of your contacts</string>
 
diff --git a/src/com/android/contacts/vcard/CancelImportActivity.java b/src/com/android/contacts/vcard/CancelImportActivity.java
new file mode 100644
index 0000000..22de0da
--- /dev/null
+++ b/src/com/android/contacts/vcard/CancelImportActivity.java
@@ -0,0 +1,129 @@
+/*
+ * 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 = "CancelImportActivity";
+
+    /* package */ final String EXTRA_TARGET_URI = "extra_target_uri";
+
+    private class CustomConnection implements ServiceConnection {
+        private Messenger mMessenger;
+        public void doBindService() {
+            bindService(new Intent(CancelImportActivity.this,
+                    VCardService.class), this, Context.BIND_AUTO_CREATE);
+        }
+        @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);
+            }
+        }
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            mMessenger = null;
+        }
+    }
+
+    private class RequestCancelImportListener implements DialogInterface.OnClickListener {
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            mConnection.doBindService();
+        }
+    }
+
+    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/CancelImportRequest.java
new file mode 100644
index 0000000..dd10187
--- /dev/null
+++ b/src/com/android/contacts/vcard/CancelImportRequest.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+/**
+ * Class representing one request for canceling vCard import (given as a Uri).
+ */
+public class CancelImportRequest {
+    public final Uri uri;
+    public CancelImportRequest(Uri uri) {
+        this.uri = uri;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/contacts/vcard/ImportProcessor.java b/src/com/android/contacts/vcard/ImportProcessor.java
index 427d62d..e19aaf1 100644
--- a/src/com/android/contacts/vcard/ImportProcessor.java
+++ b/src/com/android/contacts/vcard/ImportProcessor.java
@@ -20,11 +20,17 @@
 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;
 
@@ -54,10 +60,39 @@
  * recreate multiple instances, as this holds total count of vCard entries to be imported.
  */
 public class ImportProcessor {
-    private static final String LOG_TAG = "ImportRequestProcessor";
+    private static final String LOG_TAG = ImportProcessor.class.getSimpleName();
+
+    private class CustomConnection implements ServiceConnection {
+        private Messenger mMessenger;
+        public void doBindService() {
+            mContext.bindService(new Intent(mContext, VCardService.class),
+                    this, Context.BIND_AUTO_CREATE);
+        }
+
+        public void sendFinisheNotification() {
+            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 final CustomConnection mConnection = new CustomConnection();
+
     private ContentResolver mResolver;
     private NotificationManager mNotificationManager;
 
@@ -107,6 +142,8 @@
 
     public ImportProcessor(final Context context) {
         mContext = context;
+
+        mConnection.doBindService();
     }
 
     /**
@@ -180,6 +217,8 @@
             // imported in this procedure. Instead, we show them entries only when
             // there's just one created uri.
             doFinishNotification(mCreatedUris.size() > 0 ? mCreatedUris.get(0) : null);
+            mConnection.sendFinisheNotification();
+            mContext.unbindService(mConnection);
         } finally {
             // TODO: verify this works fine.
             mReadyForRequest = false;  // Just in case.
@@ -257,10 +296,16 @@
 
     private void doFinishNotification(final Uri createdUri) {
         final Notification notification = new Notification();
-        notification.icon = android.R.drawable.stat_sys_download_done;
+        final String title;
         notification.flags |= Notification.FLAG_AUTO_CANCEL;
 
-        final String title = mContext.getString(R.string.importing_vcard_finished_title);
+        if (isCanceled()) {
+            notification.icon = android.R.drawable.stat_notify_error;
+            title = mContext.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);
+        }
 
         final Intent intent;
         if (createdUri != null) {
diff --git a/src/com/android/contacts/vcard/ImportProgressNotifier.java b/src/com/android/contacts/vcard/ImportProgressNotifier.java
index 286bb43..99a6f55 100644
--- a/src/com/android/contacts/vcard/ImportProgressNotifier.java
+++ b/src/com/android/contacts/vcard/ImportProgressNotifier.java
@@ -23,7 +23,6 @@
 import android.widget.RemoteViews;
 
 import com.android.contacts.R;
-import com.android.contacts.activities.ContactBrowserActivity;
 import com.android.vcard.VCardEntry;
 import com.android.vcard.VCardEntryHandler;
 
@@ -97,7 +96,7 @@
 
         final PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0,
-                        new Intent(context, ContactBrowserActivity.class),
+                        new Intent(context, CancelImportActivity.class),
                         PendingIntent.FLAG_UPDATE_CURRENT);
 
         notification.contentIntent = pendingIntent;
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 6374af5..1140ff8 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -183,6 +183,7 @@
             }
         }
 
+        @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             synchronized (mPendingRequests) {
                 mMessenger = new Messenger(service);
@@ -209,6 +210,7 @@
             }
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             synchronized (mPendingRequests) {
                 if (!mPendingRequests.isEmpty()) {
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 58e1333..02daf78 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -30,10 +30,12 @@
  * The class responsible for importing vCard from one ore multiple Uris.
  */
 public class VCardService extends Service {
-    private final static String LOG_TAG = "ImportVCardService";
+    private final static String LOG_TAG = VCardService.class.getSimpleName();
 
     /* 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;
@@ -45,15 +47,26 @@
     private static final int IMPORT_NOTIFICATION_THRESHOLD = 10;
 
     public class ImportRequestHandler extends Handler {
-        private final ImportProcessor mImportProcessor =
-                new ImportProcessor(VCardService.this);
-        private final ExportProcessor mExportProcessor =
-                new ExportProcessor(VCardService.this);
+        private ImportProcessor mImportProcessor;
+        private ExportProcessor mExportProcessor = new ExportProcessor(VCardService.this);
+
+        public ImportRequestHandler() {
+            super();
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_IMPORT_REQUEST: {
                     final ImportRequest parameter = (ImportRequest)msg.obj;
+
+                    if (mImportProcessor == null) {
+                        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);
                     if (parameter.entryCount > IMPORT_NOTIFICATION_THRESHOLD) {
                         Toast.makeText(VCardService.this,
@@ -70,6 +83,14 @@
                             Toast.LENGTH_LONG).show();
                     break;
                 }
+                case MSG_CANCEL_IMPORT_REQUEST: {
+                    mImportProcessor.cancel();
+                    break;
+                }
+                case MSG_NOTIFY_IMPORT_FINISHED: {
+                    Log.d(LOG_TAG, "MSG_NOTIFY_IMPORT_FINISHED");
+                    break;
+                }
                 default: {
                     Log.e(LOG_TAG, "Unknown request type: " + msg.what);
                     super.hasMessages(msg.what);
@@ -78,7 +99,8 @@
         }
     }
 
-    private Messenger mMessenger = new Messenger(new ImportRequestHandler());
+    private ImportRequestHandler mHandler = new ImportRequestHandler();
+    private Messenger mMessenger = new Messenger(mHandler);
 
     @Override
     public int onStartCommand(Intent intent, int flags, int id) {