Refactoring again. Make importer handle ServiceConnection properly.

Bug: 2733143
Change-Id: I3189ca396da4d661a05530c02a3c46df6db24701
diff --git a/src/com/android/contacts/ImportVCardActivity.java b/src/com/android/contacts/ImportVCardActivity.java
index e021457..f263f7b 100644
--- a/src/com/android/contacts/ImportVCardActivity.java
+++ b/src/com/android/contacts/ImportVCardActivity.java
@@ -70,12 +70,12 @@
 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;
 
-
-
 /**
  * The class letting users to import vCard. This includes the UI part for letting them select
  * an Account and posssibly a file if there's no Uri is given from its caller Activity.
@@ -120,18 +120,92 @@
 
     private String mErrorMessage;
 
-    private Messenger mMessenger;
+    private class CustomConnection implements ServiceConnection {
+        private Messenger mMessenger;
+        /**
+         * Stores {@link RequestParameter} objects until actual connection is established.
+         */
+        private Queue<RequestParameter> mPendingRequests =
+                new LinkedList<RequestParameter>();
 
-    private final ServiceConnection mConnection = new ServiceConnection() {
+        private boolean mConnected = false;
+        private boolean mNeedFinish = false;
+
+        public void doBindService() {
+            // Log.d("@@@", "doBindService");
+            bindService(new Intent(ImportVCardActivity.this,
+                    ImportVCardService.class), this, Context.BIND_AUTO_CREATE);
+        }
+
+        public void setNeedFinish() {
+            synchronized (this) {
+                mNeedFinish = true;
+                if (mConnected) {
+                    unbindService(this);
+                    finish();
+                }
+            }
+        }
+
+        public synchronized void requestSend(final RequestParameter parameter) {
+            // Log.d("@@@", "requestSend(): " + (mMessenger != null) + ", "
+            // + mPendingRequests.size());
+            if (mMessenger != null) {
+                sendMessage(parameter);
+            } else {
+                mPendingRequests.add(parameter);
+            }
+        }
+
+        private void sendMessage(final RequestParameter parameter) {
+            // Log.d("@@@", "sendMessage()");
+            try {
+                mMessenger.send(Message.obtain(null,
+                        ImportVCardService.IMPORT_REQUEST,
+                        parameter));
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, "RemoteException is thrown when trying to import vCard");
+                runOnUIThread(new DialogDisplayer(
+                        getString(R.string.fail_reason_unknown)));
+            }
+        }
+
         public void onServiceConnected(ComponentName name, IBinder service) {
-            mMessenger = new Messenger(service);
+            // Log.d("@@@", "onServiceConnected()");
+            synchronized (this) {
+                mMessenger = new Messenger(service);
+                // Send pending requests thrown from this Activity before an actual connection
+                // is established.
+                while (!mPendingRequests.isEmpty()) {
+                    final RequestParameter parameter = mPendingRequests.poll();
+                    if (parameter == null) {
+                        throw new NullPointerException();
+                    }
+                    sendMessage(parameter);
+                }
+                mConnected = true;
+                if (mNeedFinish) {
+                    unbindService(this);
+                    finish();
+                }
+            }
         }
 
         public void onServiceDisconnected(ComponentName name) {
-            mMessenger = null;
-            finish();
+            // Log.d("@@@", "onServiceDisconnected()");
+            synchronized (this) {
+                if (!mPendingRequests.isEmpty()) {
+                    Log.w(LOG_TAG, "Some request(s) are dropped.");
+                }
+                // Set to null so that we can detect inappropriate re-connection toward
+                // the Service via NullPointerException;
+                mPendingRequests = null;
+                mMessenger = null;
+            }
         }
-    };
+    }
+
+    private final CustomConnection mConnection = new CustomConnection();
 
     private static class VCardFile {
         private final String mName;
@@ -206,8 +280,8 @@
             mSourceUris = sourceUris;
             final int length = sourceUris.length;
             final Context context = ImportVCardActivity.this;
-            PowerManager powerManager = (PowerManager)context.getSystemService(
-                    Context.POWER_SERVICE);
+            final PowerManager powerManager =
+                    (PowerManager)context.getSystemService(Context.POWER_SERVICE);
             mWakeLock = powerManager.newWakeLock(
                     PowerManager.SCREEN_DIM_WAKE_LOCK |
                     PowerManager.ON_AFTER_RELEASE, LOG_TAG);
@@ -226,11 +300,10 @@
             final ContentResolver resolver = context.getContentResolver();
             String errorMessage = null;
             mWakeLock.acquire();
-            boolean successful = false;
+            boolean needFinish = true;
             try {
                 clearOldCache();
-                bindService(new Intent(ImportVCardActivity.this, 
-                        ImportVCardService.class), mConnection, Context.BIND_AUTO_CREATE);
+                mConnection.doBindService();
 
                 final int length = mSourceUris.length;
                 // Uris given from caller applications may not be opened twice: consider when
@@ -247,6 +320,7 @@
                 for (int i = 0; i < length; i++) {
                     final Uri sourceUri = mSourceUris[i];
                     final Uri localDataUri = copyToLocal(sourceUri, i);
+                    // Log.d("@@@", "source: " + sourceUri);
                     if (mCanceled) {
                         break;
                     }
@@ -258,38 +332,32 @@
                     if (mCanceled) {
                         return;
                     }
-                    mMessenger.send(Message.obtain(null,
-                            ImportVCardService.IMPORT_REQUEST,
-                            parameter));
-
+                    mConnection.requestSend(parameter);
                 }
-
-                successful = true;
-            } catch (RemoteException e) {
-                Log.e(LOG_TAG, "RemoteException is thrown when trying to import vCard");
-                runOnUIThread(new DialogDisplayer(getString(R.string.fail_reason_unknown)));
             } catch (OutOfMemoryError e) {
                 Log.e(LOG_TAG, "OutOfMemoryError");
                 // We should take care of this case since Android devices may have
                 // smaller memory than we usually expect.
                 System.gc();
+                needFinish = false;
+                unbindService(mConnection);
                 runOnUIThread(new DialogDisplayer(
                         getString(R.string.fail_reason_io_error) +
                         ": " + e.getLocalizedMessage()));
             } catch (IOException e) {
                 Log.e(LOG_TAG, e.getMessage());
+                needFinish = false;
+                unbindService(mConnection);
                 runOnUIThread(new DialogDisplayer(
                         getString(R.string.fail_reason_io_error) +
                         ": " + e.getLocalizedMessage()));
             } finally {
                 mWakeLock.release();
                 mProgressDialogForCacheVCard.dismiss();
-            }
-
-            if (successful) {
-                finish();
-            } else {
-                // finish() should be called via DialogDisplayer().
+                // Log.d("@@@", "before setNeedFinish: " + needFinish);
+                if (needFinish) {
+                    mConnection.setNeedFinish();                    
+                }
             }
         }
 
@@ -657,9 +725,7 @@
     }
     
     private void importVCardFromSDCard(final VCardFile vcardFile) {
-        String[] uriStrings = new String[1];
-        uriStrings[0] = "file://" + vcardFile.getCanonicalPath();
-        importVCard(uriStrings);
+        importVCard(new Uri[] {Uri.parse("file://" + vcardFile.getCanonicalPath())});
     }
 
     private void importVCard(final Uri uri) {
diff --git a/src/com/android/contacts/ImportVCardService.java b/src/com/android/contacts/ImportVCardService.java
index f940761..e79bf82 100644
--- a/src/com/android/contacts/ImportVCardService.java
+++ b/src/com/android/contacts/ImportVCardService.java
@@ -119,9 +119,11 @@
     public class ImportRequestHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
+            Log.d("@@@", "handleMessange: " + msg.what);
             switch (msg.what) {
-                case IMPORT_REQUEST:
-                    RequestParameter parameter = (RequestParameter)msg.obj;
+                case IMPORT_REQUEST: {
+                    Log.d("@@@", "IMPORT_REQUEST");
+                    final RequestParameter parameter = (RequestParameter)msg.obj;
                     Toast.makeText(ImportVCardService.this,
                             getString(R.string.vcard_importer_start_message),
                             Toast.LENGTH_LONG).show();
@@ -143,6 +145,7 @@
                         mThread.start();
                     }
                     break;
+                }
                 default:
                     Log.e(LOG_TAG, "Unknown request type: " + msg.what);
                     super.hasMessages(msg.what);
@@ -167,10 +170,10 @@
         private ImportVCardService mService;
         private ContentResolver mResolver;
         private NotificationManager mNotificationManager;
-        private ProgressNotifier mProgressNotifier;
 
-        private final List<Uri> mFailedUris;
-        private final List<Uri> mCreatedUris;
+        private final List<Uri> mFailedUris = new ArrayList<Uri>();
+        private final List<Uri> mCreatedUris = new ArrayList<Uri>();
+        private ProgressNotifier mProgressNotifier = new ProgressNotifier();
 
         private VCardParser mVCardParser;
 
@@ -190,15 +193,6 @@
         private final Queue<RequestParameter> mPendingRequests =
                 new LinkedList<RequestParameter>();
 
-        public RequestHandler() {
-            // We cannot set Service here since Service is not fully ready at this point.
-            // TODO: refactor this class.
-
-            mFailedUris = new ArrayList<Uri>();
-            mCreatedUris = new ArrayList<Uri>();
-            mProgressNotifier = new ProgressNotifier();            
-        }
-
         public void init(ImportVCardService service) {
             // TODO: Based on fragile fact. fix this.
             mService = service;
@@ -502,6 +496,7 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        Log.d("@@@", "onBind");
         return mMessenger.getBinder();
     }
 }