Turning contact photo loader into an application-wide service.

It will no longer need to reload everything on orientation change
and other similar events.

Change-Id: Ibd4c823673d6b380df96a91a2829d24f910bcfbd
diff --git a/src/com/android/contacts/ContactPhotoLoader.java b/src/com/android/contacts/ContactPhotoManager.java
similarity index 90%
rename from src/com/android/contacts/ContactPhotoLoader.java
rename to src/com/android/contacts/ContactPhotoManager.java
index b511c65..ddd6a0e 100644
--- a/src/com/android/contacts/ContactPhotoLoader.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -16,6 +16,7 @@
 
 package com.android.contacts;
 
+import com.android.contacts.model.AccountTypeManager;
 import com.google.android.collect.Lists;
 
 import android.content.ContentResolver;
@@ -41,13 +42,71 @@
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
- * Asynchronously loads contact photos and maintains cache of photos.  The class is
- * mostly single-threaded.  The only two methods accessed by the loader thread are
- * {@link #cacheBitmap} and {@link #obtainPhotoIdsAndUrisToLoad}. Those methods access concurrent
- * hash maps shared with the main thread.
+ * Asynchronously loads contact photos and maintains a cache of photos.
  */
-public class ContactPhotoLoader implements Callback {
-    private static final String TAG = "ContactPhotoLoader";
+public abstract class ContactPhotoManager {
+
+    static final String TAG = "ContactPhotoManager";
+
+    public static final String CONTACT_PHOTO_SERVICE = "contactPhotos";
+
+    /**
+     * The resource ID of the image to be used when the photo is unavailable or being
+     * loaded.
+     */
+    protected final int mDefaultResourceId = R.drawable.ic_contact_picture;
+
+    /**
+     * Requests the singleton instance of {@link AccountTypeManager} with data bound from
+     * the available authenticators. This method can safely be called from the UI thread.
+     */
+    public static ContactPhotoManager getInstance(Context context) {
+        ContactPhotoManager service =
+                (ContactPhotoManager) context.getSystemService(CONTACT_PHOTO_SERVICE);
+        if (service == null) {
+            service = createContactPhotoManager(context);
+            Log.e(TAG, "No contact photo service in context: " + context);
+        }
+        return service;
+    }
+
+    public static synchronized ContactPhotoManager createContactPhotoManager(Context context) {
+        return new ContactPhotoManagerImpl(context);
+    }
+
+    /**
+     * Load photo into the supplied image view.  If the photo is already cached,
+     * it is displayed immediately.  Otherwise a request is sent to load the photo
+     * from the database.
+     */
+    public abstract void loadPhoto(ImageView view, long photoId);
+
+    /**
+     * Load photo into the supplied image view.  If the photo is already cached,
+     * it is displayed immediately.  Otherwise a request is sent to load the photo
+     * from the location specified by the URI.
+     */
+    public abstract void loadPhoto(ImageView view, Uri photoUri);
+
+    /**
+     * Temporarily stops loading photos from the database.
+     */
+    public abstract void pause();
+
+    /**
+     * Resumes loading photos from the database.
+     */
+    public abstract void resume();
+
+    /**
+     * Marks all cached photos for reloading.  We can continue using cache but should
+     * also make sure the photos haven't changed in the background and notify the views
+     * if so.
+     */
+    public abstract void refreshCache();
+}
+
+class ContactPhotoManagerImpl extends ContactPhotoManager implements Callback {
     private static final String LOADER_THREAD_NAME = "ContactPhotoLoader";
 
     /**
@@ -67,12 +126,6 @@
     private final String[] COLUMNS = new String[] { Photo._ID, Photo.PHOTO };
 
     /**
-     * The resource ID of the image to be used when the photo is unavailable or being
-     * loaded.
-     */
-    private final int mDefaultResourceId;
-
-    /**
      * Maintains the state of a particular photo.
      */
     private static class BitmapHolder {
@@ -86,6 +139,8 @@
         SoftReference<Bitmap> bitmapRef;
     }
 
+    private final Context mContext;
+
     /**
      * A soft cache for photos.
      */
@@ -120,25 +175,11 @@
      */
     private boolean mPaused;
 
-    private final Context mContext;
-
-    /**
-     * Constructor.
-     *
-     * @param context content context
-     * @param defaultResourceId the image resource ID to be used when there is
-     *            no photo for a contact
-     */
-    public ContactPhotoLoader(Context context, int defaultResourceId) {
-        mDefaultResourceId = defaultResourceId;
+    public ContactPhotoManagerImpl(Context context) {
         mContext = context;
     }
 
-    /**
-     * Load photo into the supplied image view.  If the photo is already cached,
-     * it is displayed immediately.  Otherwise a request is sent to load the photo
-     * from the database.
-     */
+    @Override
     public void loadPhoto(ImageView view, long photoId) {
         if (photoId == 0) {
             // No photo is needed
@@ -149,11 +190,7 @@
         }
     }
 
-    /**
-     * Load photo into the supplied image view.  If the photo is already cached,
-     * it is displayed immediately.  Otherwise a request is sent to load the photo
-     * from the location specified by the URI.
-     */
+    @Override
     public void loadPhoto(ImageView view, Uri photoUri) {
         if (photoUri == null) {
             // No photo is needed
@@ -177,11 +214,7 @@
         }
     }
 
-    /**
-     * Mark all cached photos for reloading.  We can continue using cache but should
-     * also make sure the photos haven't changed in the background and notify the views
-     * if so.
-     */
+    @Override
     public void refreshCache() {
         for (BitmapHolder holder : mBitmapCache.values()) {
             if (holder.state == BitmapHolder.LOADED) {
@@ -232,36 +265,17 @@
         return false;
     }
 
-    /**
-     * Stops loading images, kills the image loader thread and clears all caches.
-     */
-    public void stop() {
-        pause();
-
-        if (mLoaderThread != null) {
-            mLoaderThread.quit();
-            mLoaderThread = null;
-        }
-
-        mPendingRequests.clear();
-        mBitmapCache.clear();
-    }
-
     public void clear() {
         mPendingRequests.clear();
         mBitmapCache.clear();
     }
 
-    /**
-     * Temporarily stops loading photos from the database.
-     */
+    @Override
     public void pause() {
         mPaused = true;
     }
 
-    /**
-     * Resumes loading photos from the database.
-     */
+    @Override
     public void resume() {
         mPaused = false;
         if (!mPendingRequests.isEmpty()) {
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index 8346c04..42ea641 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -31,6 +31,7 @@
 
     private static InjectedServices sInjectedServices;
     private AccountTypeManager mAccountTypeManager;
+    private ContactPhotoManager mContactPhotoManager;
 
     /**
      * Overrides the system services with mocks for testing.
@@ -82,6 +83,13 @@
             return mAccountTypeManager;
         }
 
+        if (ContactPhotoManager.CONTACT_PHOTO_SERVICE.equals(name)) {
+            if (mContactPhotoManager == null) {
+                mContactPhotoManager = ContactPhotoManager.createContactPhotoManager(this);
+            }
+            return mContactPhotoManager;
+        }
+
         return super.getSystemService(name);
     }
 
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index e2cec51..a2b264b 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -15,7 +15,7 @@
  */
 package com.android.contacts.list;
 
-import com.android.contacts.ContactPhotoLoader;
+import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.R;
 import com.android.contacts.widget.IndexerListAdapter;
 import com.android.contacts.widget.TextWithHighlightingFactory;
@@ -62,7 +62,7 @@
 
     private boolean mDisplayPhotos;
     private boolean mQuickContactEnabled;
-    private ContactPhotoLoader mPhotoLoader;
+    private ContactPhotoManager mPhotoLoader;
 
     private String mQueryString;
     private char[] mUpperCaseQueryString;
@@ -226,11 +226,11 @@
         return mTextWithHighlightingFactory;
     }
 
-    public void setPhotoLoader(ContactPhotoLoader photoLoader) {
+    public void setPhotoLoader(ContactPhotoManager photoLoader) {
         mPhotoLoader = photoLoader;
     }
 
-    protected ContactPhotoLoader getPhotoLoader() {
+    protected ContactPhotoManager getPhotoLoader() {
         return mPhotoLoader;
     }
 
diff --git a/src/com/android/contacts/list/ContactEntryListFragment.java b/src/com/android/contacts/list/ContactEntryListFragment.java
index 0b9e8b0..c635d1d 100644
--- a/src/com/android/contacts/list/ContactEntryListFragment.java
+++ b/src/com/android/contacts/list/ContactEntryListFragment.java
@@ -18,7 +18,7 @@
 
 import com.android.common.widget.CompositeCursorAdapter.Partition;
 import com.android.contacts.ContactListEmptyView;
-import com.android.contacts.ContactPhotoLoader;
+import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.ContactsSearchManager;
 import com.android.contacts.R;
 import com.android.contacts.preference.ContactsPreferences;
@@ -125,7 +125,7 @@
     private int mDirectoryResultLimit = DEFAULT_DIRECTORY_RESULT_LIMIT;
 
     private ContextMenuAdapter mContextMenuAdapter;
-    private ContactPhotoLoader mPhotoLoader;
+    private ContactPhotoManager mPhotoManager;
     private ContactListEmptyView mEmptyView;
     private ProviderStatusLoader mProviderStatusLoader;
     private ContactsPreferences mContactsPrefs;
@@ -712,7 +712,7 @@
         boolean searchMode = isSearchMode();
         mAdapter.setSearchMode(searchMode);
         mAdapter.configureDefaultPartition(false, searchMode);
-        mAdapter.setPhotoLoader(mPhotoLoader);
+        mAdapter.setPhotoLoader(mPhotoManager);
         mListView.setAdapter(mAdapter);
 
         if (!isSearchMode()) {
@@ -763,14 +763,14 @@
 
     protected void configurePhotoLoader() {
         if (isPhotoLoaderEnabled() && mContext != null) {
-            if (mPhotoLoader == null) {
-                mPhotoLoader = new ContactPhotoLoader(mContext, R.drawable.ic_contact_picture);
+            if (mPhotoManager == null) {
+                mPhotoManager = ContactPhotoManager.getInstance(mContext);
             }
             if (mListView != null) {
                 mListView.setOnScrollListener(this);
             }
             if (mAdapter != null) {
-                mAdapter.setPhotoLoader(mPhotoLoader);
+                mAdapter.setPhotoLoader(mPhotoManager);
             }
         }
     }
@@ -814,29 +814,12 @@
     @Override
     public void onScrollStateChanged(AbsListView view, int scrollState) {
         if (scrollState == OnScrollListener.SCROLL_STATE_FLING) {
-            mPhotoLoader.pause();
+            mPhotoManager.pause();
         } else if (isPhotoLoaderEnabled()) {
-            mPhotoLoader.resume();
+            mPhotoManager.resume();
         }
     }
 
-    @Override
-    public void onResume() {
-        super.onResume();
-
-        if (isPhotoLoaderEnabled()) {
-            mPhotoLoader.resume();
-        }
-    }
-
-    @Override
-    public void onDestroy() {
-        if (isPhotoLoaderEnabled()) {
-            mPhotoLoader.stop();
-        }
-        super.onDestroy();
-    }
-
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         hideSoftKeyboard();