Merge "Replace enum by int"
diff --git a/src/com/android/contacts/SpecialCharSequenceMgr.java b/src/com/android/contacts/SpecialCharSequenceMgr.java
index b5deab8..aa6faec 100644
--- a/src/com/android/contacts/SpecialCharSequenceMgr.java
+++ b/src/com/android/contacts/SpecialCharSequenceMgr.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Telephony.Intents;
@@ -54,6 +55,23 @@
     private static final String TAG = "SpecialCharSequenceMgr";
     private static final String MMI_IMEI_DISPLAY = "*#06#";
 
+    /**
+     * Remembers the previous {@link QueryHandler} and cancel the operation when needed, to
+     * prevent possible crash.
+     *
+     * QueryHandler may call {@link ProgressDialog#dismiss()} when the screen is already gone,
+     * which will cause the app crash. This variable enables the class to prevent the crash
+     * on {@link #cleanup()}.
+     *
+     * TODO: Remove this and replace it (and {@link #cleanup()}) with better implementation.
+     * One complication is that we have SpecialCharSequencMgr in Phone package too, which has
+     * *slightly* different implementation. Note that Phone package doesn't have this problem,
+     * so the class on Phone side doesn't have this functionality.
+     * Fundamental fix would be to have one shared implementation and resolve this corner case more
+     * gracefully.
+     */
+    private static QueryHandler sPreviousAdnQueryHandler;
+
     /** This class is never instantiated. */
     private SpecialCharSequenceMgr() {
     }
@@ -83,6 +101,23 @@
     }
 
     /**
+     * Cleanup everything around this class. Must be run inside the main thread.
+     *
+     * This should be called when the screen becomes background.
+     */
+    public static void cleanup() {
+        if (Looper.myLooper() != Looper.getMainLooper()) {
+            Log.wtf(TAG, "cleanup() is called outside the main thread");
+            return;
+        }
+
+        if (sPreviousAdnQueryHandler != null) {
+            sPreviousAdnQueryHandler.cancel();
+            sPreviousAdnQueryHandler = null;
+        }
+    }
+
+    /**
      * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
      * If a secret code is encountered an Intent is started with the android_secret_code://<code>
      * URI.
@@ -164,6 +199,12 @@
                 // run the query.
                 handler.startQuery(ADN_QUERY_TOKEN, sc, Uri.parse("content://icc/adn"),
                         new String[]{ADN_PHONE_NUMBER_COLUMN_NAME}, null, null, null);
+
+                if (sPreviousAdnQueryHandler != null) {
+                    // It is harmless to call cancel() even after the handler's gone.
+                    sPreviousAdnQueryHandler.cancel();
+                }
+                sPreviousAdnQueryHandler = handler;
                 return true;
             } catch (NumberFormatException ex) {
                 // Ignore
@@ -304,6 +345,8 @@
      */
     private static class QueryHandler extends AsyncQueryHandler {
 
+        private boolean mCanceled;
+
         public QueryHandler(ContentResolver cr) {
             super(cr);
         }
@@ -314,6 +357,11 @@
          */
         @Override
         protected void onQueryComplete(int token, Object cookie, Cursor c) {
+            sPreviousAdnQueryHandler = null;
+            if (mCanceled) {
+                return;
+            }
+
             SimContactQueryCookie sc = (SimContactQueryCookie) cookie;
 
             // close the progress dialog.
@@ -339,5 +387,12 @@
                     .show();
             }
         }
+
+        public void cancel() {
+            mCanceled = true;
+            // Ask AsyncQueryHandler to cancel the whole request. This will fails when the
+            // query already started.
+            cancelOperation(ADN_QUERY_TOKEN);
+        }
     }
 }
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index a49ce8a..f58f3e3 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -574,6 +574,8 @@
         // TODO: I wonder if we should not check if the AsyncTask that
         // lookup the last dialed number has completed.
         mLastNumberDialed = EMPTY_NUMBER;  // Since we are going to query again, free stale number.
+
+        SpecialCharSequenceMgr.cleanup();
     }
 
     @Override
diff --git a/src/com/android/contacts/vcard/VCardService.java b/src/com/android/contacts/vcard/VCardService.java
index 71b0214..34c4acb 100644
--- a/src/com/android/contacts/vcard/VCardService.java
+++ b/src/com/android/contacts/vcard/VCardService.java
@@ -15,8 +15,6 @@
  */
 package com.android.contacts.vcard;
 
-import com.android.contacts.R;
-
 import android.app.Service;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -24,17 +22,18 @@
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.net.Uri;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.contacts.R;
 
 import java.io.File;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -108,8 +107,7 @@
 
     // Stores all unfinished import/export jobs which will be executed by mExecutorService.
     // Key is jobId.
-    private final Map<Integer, ProcessorBase> mRunningJobMap =
-            new HashMap<Integer, ProcessorBase>();
+    private final SparseArray<ProcessorBase> mRunningJobMap = new SparseArray<ProcessorBase>();
     // Stores ScannerConnectionClient objects until they finish scanning requested files.
     // Uses List class for simplicity. It's not costly as we won't have multiple objects in
     // almost all cases.
@@ -272,7 +270,9 @@
             VCardImportExportListener listener) {
         final int jobId = request.jobId;
         if (DEBUG) Log.d(LOG_TAG, String.format("Received cancel request. (id: %d)", jobId));
-        final ProcessorBase processor = mRunningJobMap.remove(jobId);
+
+        final ProcessorBase processor = mRunningJobMap.get(jobId);
+        mRunningJobMap.remove(jobId);
 
         if (processor != null) {
             processor.cancel(true);
@@ -321,16 +321,37 @@
      */
     private synchronized void stopServiceIfAppropriate() {
         if (mRunningJobMap.size() > 0) {
-            for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
-                final int jobId = entry.getKey();
-                final ProcessorBase processor = entry.getValue();
-                if (processor.isDone()) {
-                    mRunningJobMap.remove(jobId);
-                } else {
+            final int size = mRunningJobMap.size();
+
+            // Check if there are processors which aren't finished yet. If we still have ones to
+            // process, we cannot stop the service yet. Also clean up already finished processors
+            // here.
+
+            // Job-ids to be removed. At first all elements in the array are invalid and will
+            // be filled with real job-ids from the array's top. When we find a not-yet-finished
+            // processor, then we start removing those finished jobs. In that case latter half of
+            // this array will be invalid.
+            final int[] toBeRemoved = new int[size];
+            for (int i = 0; i < size; i++) {
+                final int jobId = mRunningJobMap.keyAt(i);
+                final ProcessorBase processor = mRunningJobMap.valueAt(i);
+                if (!processor.isDone()) {
                     Log.i(LOG_TAG, String.format("Found unfinished job (id: %d)", jobId));
+
+                    // Remove processors which are already "done", all of which should be before
+                    // processors which aren't done yet.
+                    for (int j = 0; j < i; j++) {
+                        mRunningJobMap.remove(toBeRemoved[j]);
+                    }
                     return;
                 }
+
+                // Remember the finished processor.
+                toBeRemoved[i] = jobId;
             }
+
+            // We're sure we can remove all. Instead of removing one by one, just call clear().
+            mRunningJobMap.clear();
         }
 
         if (!mRemainingScannerConnections.isEmpty()) {
@@ -374,9 +395,7 @@
             Log.d(LOG_TAG, String.format("Received vCard import finish notification (id: %d). "
                     + "Result: %b", jobId, (successful ? "success" : "failure")));
         }
-        if (mRunningJobMap.remove(jobId) == null) {
-            Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
-        }
+        mRunningJobMap.remove(jobId);
         stopServiceIfAppropriate();
     }
 
@@ -386,7 +405,8 @@
             Log.d(LOG_TAG, String.format("Received vCard export finish notification (id: %d). "
                     + "Result: %b", jobId, (successful ? "success" : "failure")));
         }
-        final ProcessorBase job = mRunningJobMap.remove(jobId);
+        final ProcessorBase job = mRunningJobMap.get(jobId);
+        mRunningJobMap.remove(jobId);
         if (job == null) {
             Log.w(LOG_TAG, String.format("Tried to remove unknown job (id: %d)", jobId));
         } else if (!(job instanceof ExportProcessor)) {
@@ -408,8 +428,8 @@
      * Mainly called from onDestroy().
      */
     private synchronized void cancelAllRequestsAndShutdown() {
-        for (final Map.Entry<Integer, ProcessorBase> entry : mRunningJobMap.entrySet()) {
-            entry.getValue().cancel(true);
+        for (int i = 0; i < mRunningJobMap.size(); i++) {
+            mRunningJobMap.valueAt(i).cancel(true);
         }
         mRunningJobMap.clear();
         mExecutorService.shutdown();