Make multi import one request

VCardService may exit after processing the first request for
multiple imports, in which the user may see strange "rejected"
messages. We should pack those multiple import into one message,
which forces VCardService to process after all the requests.

Bug: 4540627
Change-Id: I4d645eb3a04e35da465b0c4181cbff5bf7ebf029
diff --git a/src/com/android/contacts/vcard/ImportVCardActivity.java b/src/com/android/contacts/vcard/ImportVCardActivity.java
index 17b89c3..1397dd7 100644
--- a/src/com/android/contacts/vcard/ImportVCardActivity.java
+++ b/src/com/android/contacts/vcard/ImportVCardActivity.java
@@ -188,12 +188,12 @@
     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));
+        public void sendImportRequest(final List<ImportRequest> requests) {
+            Log.i(LOG_TAG, "Send an import request");
             try {
                 mMessenger.send(Message.obtain(null,
                         VCardService.MSG_IMPORT_REQUEST,
-                        request));
+                        requests));
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "RemoteException is thrown when trying to send request");
                 runOnUiThread(new DialogDisplayer(getString(R.string.fail_reason_unknown)));
@@ -273,6 +273,7 @@
                 // We may be able to read content of each vCard file during copying them
                 // to local storage, but currently vCard code does not allow us to do so.
                 int cache_index = 0;
+                ArrayList<ImportRequest> requests = new ArrayList<ImportRequest>();
                 for (Uri sourceUri : mSourceUris) {
                     String filename = null;
                     // Note: caches are removed by VCardService.
@@ -297,9 +298,9 @@
                         Log.w(LOG_TAG, "destUri is null");
                         break;
                     }
-                    final ImportRequest parameter;
+                    final ImportRequest request;
                     try {
-                        parameter = constructImportRequest(localDataUri, sourceUri);
+                        request = constructImportRequest(localDataUri, sourceUri);
                     } catch (VCardException e) {
                         Log.e(LOG_TAG, "Maybe the file is in wrong format", e);
                         showFailureNotification(R.string.fail_reason_not_supported);
@@ -313,7 +314,12 @@
                         Log.i(LOG_TAG, "vCard cache operation is canceled.");
                         return;
                     }
-                    mConnection.sendImportRequest(parameter);
+                    requests.add(request);
+                }
+                if (!requests.isEmpty()) {
+                    mConnection.sendImportRequest(requests);
+                } else {
+                    Log.w(LOG_TAG, "Empty import requests. Ignore it.");
                 }
             } catch (OutOfMemoryError e) {
                 Log.e(LOG_TAG, "OutOfMemoryError occured during caching vCard");
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index a4c0480..e927757 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -59,7 +59,7 @@
 // works fine enough. Investigate the feasibility.
 public class VCardService extends Service {
     private final static String LOG_TAG = "VCardService";
-    /* package */ final static boolean DEBUG = true;
+    /* package */ final static boolean DEBUG = false;
 
     /* package */ static final int MSG_IMPORT_REQUEST = 1;
     /* package */ static final int MSG_EXPORT_REQUEST = 2;
@@ -81,7 +81,7 @@
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case MSG_IMPORT_REQUEST: {
-                    handleImportRequest((ImportRequest)msg.obj);
+                    handleImportRequest((List<ImportRequest>)msg.obj);
                     break;
                 }
                 case MSG_EXPORT_REQUEST: {
@@ -217,40 +217,57 @@
         super.onDestroy();
     }
 
-    private synchronized void handleImportRequest(ImportRequest request) {
+    private synchronized void handleImportRequest(List<ImportRequest> requests) {
         if (DEBUG) {
-            Log.d(LOG_TAG,
-                    String.format("received import request (uri: %s, originalUri: %s)",
-                            request.uri, request.originalUri));
-        }
-        if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) {
-            final String displayName;
-            final String message;
-            final String lastPathSegment = request.originalUri.getLastPathSegment();
-            if ("file".equals(request.originalUri.getScheme()) &&
-                    lastPathSegment != null) {
-                displayName = lastPathSegment;
-                message = getString(R.string.vcard_import_will_start_message, displayName);
-            } else {
-                displayName = getString(R.string.vcard_unknown_filename);
-                message = getString(R.string.vcard_import_will_start_message_with_default_name);
+            final ArrayList<String> uris = new ArrayList<String>();
+            final ArrayList<String> originalUris = new ArrayList<String>();
+            for (ImportRequest request : requests) {
+                uris.add(request.uri.toString());
+                originalUris.add(request.originalUri.toString());
             }
+            Log.d(LOG_TAG,
+                    String.format("received multiple import request (uri: %s, originalUri: %s)",
+                            uris.toString(), originalUris.toString()));
+        }
+        final int size = requests.size();
+        for (int i = 0; i < size; i++) {
+            ImportRequest request = requests.get(i);
 
-            // 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();
+            if (tryExecute(new ImportProcessor(this, request, mCurrentJobId))) {
+                final String displayName;
+                final String message;
+                final String lastPathSegment = request.originalUri.getLastPathSegment();
+                if ("file".equals(request.originalUri.getScheme()) &&
+                        lastPathSegment != null) {
+                    displayName = lastPathSegment;
+                    message = getString(R.string.vcard_import_will_start_message, displayName);
+                } else {
+                    displayName = getString(R.string.vcard_unknown_filename);
+                    message = getString(
+                            R.string.vcard_import_will_start_message_with_default_name);
+                }
 
-            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();
+                // We just want to show notification for the first vCard.
+                if (i == 0) {
+                    // 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();
+                // A rejection means executor doesn't run any more. Exit.
+                break;
+            }
         }
     }
 
@@ -289,6 +306,10 @@
      */
     private synchronized boolean tryExecute(ProcessorBase processor) {
         try {
+            if (DEBUG) {
+                Log.d(LOG_TAG, "Executor service status: shutdown: " + mExecutorService.isShutdown()
+                        + ", terminated: " + mExecutorService.isTerminated());
+            }
             mExecutorService.execute(processor);
             mRunningJobMap.put(mCurrentJobId, processor);
             return true;