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();