Merge "Use SparseArray instead of HashMap in vCard code"
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