/*****************************************************************************
* BitmapCache.java
*****************************************************************************
* Copyright © 2012 VLC authors and VideoLAN
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
package org.videolan.vlc.util;
import android.annotation.TargetApi;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.support.v4.util.LruCache;
import android.text.TextUtils;
import android.util.Log;
import org.videolan.libvlc.util.AndroidUtil;
import org.videolan.vlc.R;
import org.videolan.vlc.VLCApplication;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
public class BitmapCache {
public final static String TAG = "VLC/BitmapCache";
private final static boolean LOG_ENABLED = false;
private static final String CONE_KEY = "res:"+ R.drawable.cone;
private static final String CONE_O_KEY = "res:"+ R.drawable.ic_cone_o;
private static BitmapCache mInstance;
private final LruCache<String, CacheableBitmap> mMemCache;
Set<SoftReference<Bitmap>> mCachedBitmaps;
Set<SoftReference<Bitmap>> mReusableBitmaps;
public synchronized static BitmapCache getInstance() {
if (mInstance == null)
mInstance = new BitmapCache();
return mInstance;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private BitmapCache() {
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final ActivityManager am = ((ActivityManager) VLCApplication.getAppContext().getSystemService(
Context.ACTIVITY_SERVICE));
final int memClass = AndroidUtil.isHoneycombOrLater() ? am.getLargeMemoryClass() : am.getMemoryClass();
// Use 1/5th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 5;
Log.i(TAG, "LRUCache size set to " + cacheSize);
mMemCache = new LruCache<String, CacheableBitmap>(cacheSize) {
@Override
protected int sizeOf(String key, CacheableBitmap value) {
return value.getSize();
}
@Override
protected void entryRemoved(boolean evicted, String key, CacheableBitmap oldValue, CacheableBitmap newValue) {
if (evicted) {
mCachedBitmaps.remove(oldValue.getReference());
if (mReusableBitmaps != null && oldValue.get() != null && !TextUtils.equals(key, CONE_KEY) && !TextUtils.equals(key, CONE_O_KEY))
addReusableBitmapRef(oldValue.getReference());
}
}
};
if (AndroidUtil.isHoneycombOrLater())
mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
mCachedBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
public synchronized Bitmap getBitmapFromMemCache(String key) {
final CacheableBitmap cacheableBitmap = mMemCache.get(key);
if (cacheableBitmap == null)
return null;
Bitmap b = cacheableBitmap.get();
if (b == null){
mMemCache.remove(key);
mCachedBitmaps.remove(cacheableBitmap.getReference());
return null;
}
if (b.isRecycled()) {
/* A recycled bitmap cannot be used again */
addReusableBitmapRef(cacheableBitmap.getReference());
mCachedBitmaps.remove(cacheableBitmap.getReference());
mMemCache.remove(key);
b = null;
}
if (LOG_ENABLED)
Log.d(TAG, (b == null) ? "Cache miss" : "Cache found");
return b;
}
public synchronized void addBitmapToMemCache(String key, Bitmap bitmap) {
if (key != null && bitmap != null && getBitmapFromMemCache(key) == null) {
final CacheableBitmap cacheableBitmap = new CacheableBitmap(bitmap);
mMemCache.put(key, cacheableBitmap);
mCachedBitmaps.add(cacheableBitmap.getReference());
}
}
private Bitmap getBitmapFromMemCache(int resId) {
return getBitmapFromMemCache("res:" + resId);
}
private void addBitmapToMemCache(int resId, Bitmap bitmap) {
addBitmapToMemCache("res:" + resId, bitmap);
}
public synchronized void clear() {
mMemCache.evictAll();
mCachedBitmaps.clear();
}
public static Bitmap getFromResource(Resources res, int resId) {
BitmapCache cache = BitmapCache.getInstance();
Bitmap bitmap = cache.getBitmapFromMemCache(resId);
if (bitmap == null) {
BitmapFactory.Options options = new BitmapFactory.Options();
BitmapUtil.setInBitmap(options);
if (AndroidUtil.isHoneycombOrLater())
options.inMutable = true;
bitmap = BitmapFactory.decodeResource(res, resId, options);
cache.addBitmapToMemCache(resId, bitmap);
}
return bitmap;
}
private synchronized void addReusableBitmapRef(SoftReference<Bitmap> ref){
mReusableBitmaps.add(ref);
}
public synchronized Bitmap getReusableBitmap(BitmapFactory.Options targetOptions){
if (mReusableBitmaps == null || mReusableBitmaps.isEmpty())
return null;
Bitmap reusable = null;
LinkedList<SoftReference<Bitmap>> itemsToRemove = new LinkedList<SoftReference<Bitmap>>();
for(SoftReference<Bitmap> b : mReusableBitmaps){
reusable = b.get();
if (reusable == null) {
itemsToRemove.add(b);
continue;
}
// if (!reusable.isRecycled()) {
// Log.d(TAG, "not recycled");
// itemsToRemove.add(b);
// continue;
// }
if (BitmapUtil.canUseForInBitmap(reusable, targetOptions)) {
itemsToRemove.add(b);
return reusable;
}
}
if (!itemsToRemove.isEmpty())
mReusableBitmaps.removeAll(itemsToRemove);
return null;
}
private static class CacheableBitmap {
final int mSize;
final SoftReference<Bitmap> mReference;
CacheableBitmap(Bitmap bitmap){
mReference = new SoftReference<Bitmap>(bitmap);
mSize = bitmap == null ? 0 : bitmap.getRowBytes() * bitmap.getHeight();
}
public SoftReference<Bitmap> getReference(){
return mReference;
}
public Bitmap get(){
if (mReference != null)
return mReference.get();
return null;
}
public int getSize(){
return mSize;
}
}
}