Merge "Fix cursor leak."
diff --git a/src/com/android/contacts/calllog/CallLogQueryHandler.java b/src/com/android/contacts/calllog/CallLogQueryHandler.java
index affdd1d..edc631f 100644
--- a/src/com/android/contacts/calllog/CallLogQueryHandler.java
+++ b/src/com/android/contacts/calllog/CallLogQueryHandler.java
@@ -59,7 +59,6 @@
private static final int UPDATE_MARK_VOICEMAILS_AS_OLD_TOKEN = 56;
/** The token for the query to mark all missed calls as read after seeing the call log. */
private static final int UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN = 57;
-
/** The token for the query to fetch voicemail status messages. */
private static final int QUERY_VOICEMAIL_STATUS_TOKEN = 58;
@@ -75,6 +74,24 @@
@GuardedBy("this") private Cursor mNewCallsCursor;
/** The cursor containing the old calls, or null if they have not yet been fetched. */
@GuardedBy("this") private Cursor mOldCallsCursor;
+ /**
+ * The identifier of the latest calls request.
+ * <p>
+ * A request for the list of calls requires two queries and hence the two cursor
+ * {@link #mNewCallsCursor} and {@link #mOldCallsCursor} above, corresponding to
+ * {@link #QUERY_NEW_CALLS_TOKEN} and {@link #QUERY_OLD_CALLS_TOKEN}.
+ * <p>
+ * When a new request is about to be started, existing cursors are closed. However, it is
+ * possible that one of the queries completes after the new request has started. This means that
+ * we might merge two cursors that do not correspond to the same request. Moreover, this may
+ * lead to a resource leak if the same query completes and we override the cursor without
+ * closing it first.
+ * <p>
+ * To make sure we only join two cursors from the same request, we use this variable to store
+ * the request id of the latest request and make sure we only process cursors corresponding to
+ * the this request.
+ */
+ @GuardedBy("this") private int mCallsRequestId;
/**
* Simple handler that wraps background calls to catch
@@ -141,9 +158,9 @@
*/
public void fetchAllCalls() {
cancelFetch();
- invalidate();
- fetchCalls(QUERY_NEW_CALLS_TOKEN, true /*isNew*/, false /*voicemailOnly*/);
- fetchCalls(QUERY_OLD_CALLS_TOKEN, false /*isNew*/, false /*voicemailOnly*/);
+ int requestId = newCallsRequest();
+ fetchCalls(QUERY_NEW_CALLS_TOKEN, requestId, true /*isNew*/, false /*voicemailOnly*/);
+ fetchCalls(QUERY_OLD_CALLS_TOKEN, requestId, false /*isNew*/, false /*voicemailOnly*/);
}
/**
@@ -153,9 +170,9 @@
*/
public void fetchVoicemailOnly() {
cancelFetch();
- invalidate();
- fetchCalls(QUERY_NEW_CALLS_TOKEN, true /*isNew*/, true /*voicemailOnly*/);
- fetchCalls(QUERY_OLD_CALLS_TOKEN, false /*isNew*/, true /*voicemailOnly*/);
+ int requestId = newCallsRequest();
+ fetchCalls(QUERY_NEW_CALLS_TOKEN, requestId, true /*isNew*/, true /*voicemailOnly*/);
+ fetchCalls(QUERY_OLD_CALLS_TOKEN, requestId, false /*isNew*/, true /*voicemailOnly*/);
}
@@ -165,7 +182,7 @@
}
/** Fetches the list of calls in the call log, either the new one or the old ones. */
- private void fetchCalls(int token, boolean isNew, boolean voicemailOnly) {
+ private void fetchCalls(int token, int requestId, boolean isNew, boolean voicemailOnly) {
// We need to check for NULL explicitly otherwise entries with where READ is NULL
// may not match either the query or its negation.
// We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".
@@ -182,7 +199,7 @@
selection = String.format("(%s) AND (%s = ?)", selection, Calls.TYPE);
selectionArgs.add(Integer.toString(Calls.VOICEMAIL_TYPE));
}
- startQuery(token, null, Calls.CONTENT_URI_WITH_VOICEMAIL,
+ startQuery(token, requestId, Calls.CONTENT_URI_WITH_VOICEMAIL,
CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),
Calls.DEFAULT_SORT_ORDER);
}
@@ -239,25 +256,41 @@
}
/**
- * Invalidate the current list of calls.
+ * Start a new request and return its id. The request id will be used as the cookie for the
+ * background request.
* <p>
- * This method is synchronized because it must close the cursors and reset them atomically.
+ * Closes any open cursor that has not yet been sent to the requester.
*/
- private synchronized void invalidate() {
+ private synchronized int newCallsRequest() {
MoreCloseables.closeQuietly(mNewCallsCursor);
MoreCloseables.closeQuietly(mOldCallsCursor);
mNewCallsCursor = null;
mOldCallsCursor = null;
+ return ++mCallsRequestId;
}
@Override
protected synchronized void onQueryComplete(int token, Object cookie, Cursor cursor) {
if (token == QUERY_NEW_CALLS_TOKEN) {
+ int requestId = ((Integer) cookie).intValue();
+ if (requestId != mCallsRequestId) {
+ // Ignore this query since it does not correspond to the latest request.
+ return;
+ }
+
// Store the returned cursor.
+ MoreCloseables.closeQuietly(mNewCallsCursor);
mNewCallsCursor = new ExtendedCursor(
cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_NEW_ITEM);
} else if (token == QUERY_OLD_CALLS_TOKEN) {
+ int requestId = ((Integer) cookie).intValue();
+ if (requestId != mCallsRequestId) {
+ // Ignore this query since it does not correspond to the latest request.
+ return;
+ }
+
// Store the returned cursor.
+ MoreCloseables.closeQuietly(mOldCallsCursor);
mOldCallsCursor = new ExtendedCursor(
cursor, CallLogQuery.SECTION_NAME, CallLogQuery.SECTION_OLD_ITEM);
} else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {