Use android.util.LruCache instead of LinkedHashMap.

Change-Id: Ibfa1550ae303069ecfc17c7a4958d64d36fbccc9
http://b/3184897
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index 4df7bbb..7f1387d 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -36,6 +36,7 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.Directory;
 import android.util.Log;
+import android.util.LruCache;
 import android.widget.ImageView;
 
 import java.io.ByteArrayOutputStream;
@@ -43,8 +44,6 @@
 import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -155,20 +154,24 @@
 
     private final Context mContext;
 
-    private static final int BITMAP_CACHE_INITIAL_CAPACITY = 32;
-
     /**
      * An LRU cache for bitmap holders. The cache contains bytes for photos just
-     * as they come from the database. It also softly retains the actual bitmap.
+     * as they come from the database. Each holder has a soft reference to the
+     * actual bitmap.
      */
-    private final BitmapHolderCache mBitmapHolderCache;
+    private final LruCache<Object, BitmapHolder> mBitmapHolderCache;
+
+    /**
+     * Cache size threshold at which bitmaps will not be preloaded.
+     */
+    private final int mBitmapHolderCacheRedZoneBytes;
 
     /**
      * Level 2 LRU cache for bitmaps. This is a smaller cache that holds
      * the most recently used bitmaps to save time on decoding
      * them from bytes (the bytes are stored in {@link #mBitmapHolderCache}.
      */
-    private final BitmapCache mBitmapCache;
+    private final LruCache<Object, Bitmap> mBitmapCache;
 
     /**
      * A map from ImageView to the corresponding photo ID. Please note that this
@@ -202,10 +205,15 @@
         mContext = context;
 
         Resources resources = context.getResources();
-        mBitmapCache = new BitmapCache(
+        mBitmapCache = new LruCache<Object, Bitmap>(
                 resources.getInteger(R.integer.config_photo_cache_max_bitmaps));
-        mBitmapHolderCache = new BitmapHolderCache(mBitmapCache,
-                resources.getInteger(R.integer.config_photo_cache_max_bytes));
+        int maxBytes = resources.getInteger(R.integer.config_photo_cache_max_bytes);
+        mBitmapHolderCache = new LruCache<Object, BitmapHolder>(maxBytes) {
+            @Override protected int sizeOf(Object key, BitmapHolder value) {
+                return value.bytes.length;
+            }
+        };
+        mBitmapHolderCacheRedZoneBytes = (int) (maxBytes * 0.75);
     }
 
     @Override
@@ -251,7 +259,7 @@
 
     @Override
     public void refreshCache() {
-        for (BitmapHolder holder : mBitmapHolderCache.values()) {
+        for (BitmapHolder holder : mBitmapHolderCache.snapshot().values()) {
             holder.fresh = false;
         }
     }
@@ -319,7 +327,7 @@
 
     public void clear() {
         mPendingRequests.clear();
-        mBitmapHolderCache.clear();
+        mBitmapHolderCache.evictAll();
     }
 
     @Override
@@ -406,7 +414,7 @@
      * if needed.  Some of the bitmaps will still be retained by {@link #mBitmapCache}.
      */
     private void softenCache() {
-        for (BitmapHolder holder : mBitmapHolderCache.values()) {
+        for (BitmapHolder holder : mBitmapHolderCache.snapshot().values()) {
             holder.bitmap = null;
         }
     }
@@ -580,7 +588,7 @@
                 return;
             }
 
-            if (mBitmapHolderCache.isFull()) {
+            if (mBitmapHolderCache.size() > mBitmapHolderCacheRedZoneBytes) {
                 mPreloadStatus = PRELOAD_STATUS_DONE;
                 return;
             }
@@ -607,7 +615,7 @@
 
             Log.v(TAG, "Preloaded " + count + " photos. Photos in cache: "
                     + mBitmapHolderCache.size()
-                    + ". Total size: " + mBitmapHolderCache.getEstimatedSize());
+                    + ". Total size: " + mBitmapHolderCache.size());
 
             requestPreloading();
         }
@@ -735,82 +743,4 @@
             }
         }
     }
-
-    /**
-     * An LRU cache of {@link BitmapHolder}'s.  It estimates the total size of
-     * loaded bitmaps and caps that number.
-     */
-    private static class BitmapHolderCache extends LinkedHashMap<Object, BitmapHolder> {
-        private final BitmapCache mBitmapCache;
-        private final int mMaxBytes;
-        private final int mRedZoneBytes;
-        private int mEstimatedBytes;
-
-        public BitmapHolderCache(BitmapCache bitmapCache, int maxBytes) {
-            super(BITMAP_CACHE_INITIAL_CAPACITY, 0.75f, true);
-            this.mBitmapCache = bitmapCache;
-            mMaxBytes = maxBytes;
-            mRedZoneBytes = (int) (mMaxBytes * 0.75);
-        }
-
-        // Leave unsynchronized: if the result is a bit off, that's ok
-        public boolean isFull() {
-            return mEstimatedBytes > mRedZoneBytes;
-        }
-
-        public int getEstimatedSize() {
-            return mEstimatedBytes;
-        }
-
-        @Override
-        public synchronized BitmapHolder get(Object key) {
-            return super.get(key);
-        }
-
-        @Override
-        public synchronized BitmapHolder put(Object key, BitmapHolder newValue) {
-            BitmapHolder oldValue = get(key);
-            if (oldValue != null && oldValue.bytes != null) {
-                mEstimatedBytes -= oldValue.bytes.length;
-            }
-            if (newValue.bytes != null) {
-                mEstimatedBytes += newValue.bytes.length;
-            }
-            return super.put(key, newValue);
-        }
-
-        @Override
-        public BitmapHolder remove(Object key) {
-            BitmapHolder value = get(key);
-            if (value != null && value.bytes != null) {
-                mEstimatedBytes -= value.bytes.length;
-            }
-            mBitmapCache.remove(key);
-            return super.remove(key);
-        }
-
-        @Override
-        protected boolean removeEldestEntry(Map.Entry<Object, BitmapHolder> eldest) {
-            return mEstimatedBytes > mMaxBytes;
-        }
-    }
-
-    /**
-     * An LRU cache of bitmaps.  These are the most recently used bitmaps that we want
-     * to protect from GC. The rest of bitmaps are softly retained and will be
-     * gradually released by GC.
-     */
-    private static class BitmapCache extends LinkedHashMap<Object, Bitmap> {
-        private int mMaxEntries;
-
-        public BitmapCache(int maxEntries) {
-            super(BITMAP_CACHE_INITIAL_CAPACITY, 0.75f, true);
-            mMaxEntries = maxEntries;
-        }
-
-        @Override
-        protected boolean removeEldestEntry(Map.Entry<Object, Bitmap> eldest) {
-            return size() > mMaxEntries;
-        }
-    }
 }