Merge "Avoid spawning extraneous threads in CallLogAdapter."
diff --git a/src/com/android/contacts/calllog/CallLogAdapter.java b/src/com/android/contacts/calllog/CallLogAdapter.java
index 6105b03..8c5005e 100644
--- a/src/com/android/contacts/calllog/CallLogAdapter.java
+++ b/src/com/android/contacts/calllog/CallLogAdapter.java
@@ -48,7 +48,7 @@
* Adapter class to fill in data for the Call Log.
*/
/*package*/ class CallLogAdapter extends GroupingListAdapter
- implements Runnable, ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
+ implements ViewTreeObserver.OnPreDrawListener, CallLogGroupBuilder.GroupCreator {
/** Interface used to initiate a refresh of the content. */
public interface CallFetcher {
public void fetchCalls();
@@ -94,6 +94,7 @@
private final Context mContext;
private final ContactInfoHelper mContactInfoHelper;
private final CallFetcher mCallFetcher;
+ private ViewTreeObserver mViewTreeObserver = null;
/**
* A cache of the contact details for the phone numbers in the call log.
@@ -159,14 +160,11 @@
*/
private final LinkedList<ContactInfoRequest> mRequests;
- private volatile boolean mDone;
private boolean mLoading = true;
- private ViewTreeObserver.OnPreDrawListener mPreDrawListener;
private static final int REDRAW = 1;
private static final int START_THREAD = 2;
- private boolean mFirst;
- private Thread mCallerIdThread;
+ private QueryThread mCallerIdThread;
/** Instance of helper class for managing views. */
private final CallLogListItemHelper mCallLogViewsHelper;
@@ -204,11 +202,15 @@
@Override
public boolean onPreDraw() {
- if (mFirst) {
- mHandler.sendEmptyMessageDelayed(START_THREAD,
- START_PROCESSING_REQUESTS_DELAY_MILLIS);
- mFirst = false;
+ // We only wanted to listen for the first draw (and this is it).
+ unregisterPreDrawListener();
+
+ // Only schedule a thread-creation message if the thread hasn't been
+ // created yet. This is purely an optimization, to queue fewer messages.
+ if (mCallerIdThread == null) {
+ mHandler.sendEmptyMessageDelayed(START_THREAD, START_PROCESSING_REQUESTS_DELAY_MILLIS);
}
+
return true;
}
@@ -236,7 +238,6 @@
mContactInfoCache = ExpirableCache.create(CONTACT_INFO_CACHE_SIZE);
mRequests = new LinkedList<ContactInfoRequest>();
- mPreDrawListener = null;
Resources resources = mContext.getResources();
CallTypeHelper callTypeHelper = new CallTypeHelper(resources);
@@ -273,35 +274,53 @@
}
}
- private void startRequestProcessing() {
- if (mRequestProcessingDisabled) {
- return;
- }
+ /**
+ * Starts a background thread to process contact-lookup requests, unless one
+ * has already been started.
+ */
+ private synchronized void startRequestProcessing() {
+ // For unit-testing.
+ if (mRequestProcessingDisabled) return;
- mDone = false;
- mCallerIdThread = new Thread(this, "CallLogContactLookup");
+ // Idempotence... if a thread is already started, don't start another.
+ if (mCallerIdThread != null) return;
+
+ mCallerIdThread = new QueryThread();
mCallerIdThread.setPriority(Thread.MIN_PRIORITY);
mCallerIdThread.start();
}
/**
- * Stops the background thread that processes updates and cancels any pending requests to
- * start it.
- * <p>
- * Should be called from the main thread to prevent a race condition between the request to
- * start the thread being processed and stopping the thread.
+ * Stops the background thread that processes updates and cancels any
+ * pending requests to start it.
*/
- public void stopRequestProcessing() {
+ public synchronized void stopRequestProcessing() {
// Remove any pending requests to start the processing thread.
mHandler.removeMessages(START_THREAD);
- mDone = true;
- if (mCallerIdThread != null) mCallerIdThread.interrupt();
+ if (mCallerIdThread != null) {
+ // Stop the thread; we are finished with it.
+ mCallerIdThread.stopProcessing();
+ mCallerIdThread.interrupt();
+ mCallerIdThread = null;
+ }
+ }
+
+ /**
+ * Stop receiving onPreDraw() notifications.
+ */
+ private void unregisterPreDrawListener() {
+ if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) {
+ mViewTreeObserver.removeOnPreDrawListener(this);
+ }
+ mViewTreeObserver = null;
}
public void invalidateCache() {
mContactInfoCache.expireAll();
- // Let it restart the thread after next draw
- mPreDrawListener = null;
+
+ // Restart the request-processing thread after the next draw.
+ stopRequestProcessing();
+ unregisterPreDrawListener();
}
/**
@@ -323,10 +342,7 @@
mRequests.notifyAll();
}
}
- if (mFirst && immediate) {
- startRequestProcessing();
- mFirst = false;
- }
+ if (immediate) startRequestProcessing();
}
/**
@@ -362,34 +378,60 @@
updateCallLogContactInfoCache(number, countryIso, info, callLogInfo);
return updated;
}
+
/*
- * Handles requests for contact name and number type
- * @see java.lang.Runnable#run()
+ * Handles requests for contact name and number type.
*/
- @Override
- public void run() {
- boolean needNotify = false;
- while (!mDone) {
- ContactInfoRequest request = null;
- synchronized (mRequests) {
- if (!mRequests.isEmpty()) {
- request = mRequests.removeFirst();
- } else {
- if (needNotify) {
- needNotify = false;
- mHandler.sendEmptyMessage(REDRAW);
- }
- try {
- mRequests.wait(1000);
- } catch (InterruptedException ie) {
- // Ignore and continue processing requests
- Thread.currentThread().interrupt();
+ private class QueryThread extends Thread {
+ private volatile boolean mDone = false;
+
+ public QueryThread() {
+ super("CallLogAdapter.QueryThread");
+ }
+
+ public void stopProcessing() {
+ mDone = true;
+ }
+
+ @Override
+ public void run() {
+ boolean needRedraw = false;
+ while (true) {
+ // Check if thread is finished, and if so return immediately.
+ if (mDone) return;
+
+ // Obtain next request, if any is available.
+ // Keep synchronized section small.
+ ContactInfoRequest req = null;
+ synchronized (mRequests) {
+ if (!mRequests.isEmpty()) {
+ req = mRequests.removeFirst();
}
}
- }
- if (!mDone && request != null
- && queryContactInfo(request.number, request.countryIso, request.callLogInfo)) {
- needNotify = true;
+
+ if (req != null) {
+ // Process the request. If the lookup succeeds, schedule a
+ // redraw.
+ needRedraw |= queryContactInfo(req.number, req.countryIso, req.callLogInfo);
+ } else {
+ // Throttle redraw rate by only sending them when there are
+ // more requests.
+ if (needRedraw) {
+ needRedraw = false;
+ mHandler.sendEmptyMessage(REDRAW);
+ }
+
+ // Wait until another request is available, or until this
+ // thread is no longer needed (as indicated by being
+ // interrupted).
+ try {
+ synchronized (mRequests) {
+ mRequests.wait(1000);
+ }
+ } catch (InterruptedException ie) {
+ // Ignore, and attempt to continue processing requests.
+ }
+ }
}
}
}
@@ -567,10 +609,9 @@
setPhoto(views, photoId, lookupUri);
// Listen for the first draw
- if (mPreDrawListener == null) {
- mFirst = true;
- mPreDrawListener = this;
- view.getViewTreeObserver().addOnPreDrawListener(this);
+ if (mViewTreeObserver == null) {
+ mViewTreeObserver = view.getViewTreeObserver();
+ mViewTreeObserver.addOnPreDrawListener(this);
}
}
@@ -647,9 +688,7 @@
needsUpdate = true;
}
- if (!needsUpdate) {
- return;
- }
+ if (!needsUpdate) return;
if (countryIso == null) {
mContext.getContentResolver().update(Calls.CONTENT_URI_WITH_VOICEMAIL, values,