1*90c8c64dSAndroid Build Coastguard Worker /* 2*90c8c64dSAndroid Build Coastguard Worker * Copyright (C) 2014 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker * 4*90c8c64dSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker * You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker * 8*90c8c64dSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker * 10*90c8c64dSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker * limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker */ 16*90c8c64dSAndroid Build Coastguard Worker 17*90c8c64dSAndroid Build Coastguard Worker package com.example.android.mediabrowserservice; 18*90c8c64dSAndroid Build Coastguard Worker 19*90c8c64dSAndroid Build Coastguard Worker import android.graphics.Bitmap; 20*90c8c64dSAndroid Build Coastguard Worker import android.os.AsyncTask; 21*90c8c64dSAndroid Build Coastguard Worker import android.util.LruCache; 22*90c8c64dSAndroid Build Coastguard Worker 23*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.BitmapHelper; 24*90c8c64dSAndroid Build Coastguard Worker import com.example.android.mediabrowserservice.utils.LogHelper; 25*90c8c64dSAndroid Build Coastguard Worker 26*90c8c64dSAndroid Build Coastguard Worker import java.io.IOException; 27*90c8c64dSAndroid Build Coastguard Worker 28*90c8c64dSAndroid Build Coastguard Worker /** 29*90c8c64dSAndroid Build Coastguard Worker * Implements a basic cache of album arts, with async loading support. 30*90c8c64dSAndroid Build Coastguard Worker */ 31*90c8c64dSAndroid Build Coastguard Worker public final class AlbumArtCache { 32*90c8c64dSAndroid Build Coastguard Worker private static final String TAG = LogHelper.makeLogTag(AlbumArtCache.class); 33*90c8c64dSAndroid Build Coastguard Worker 34*90c8c64dSAndroid Build Coastguard Worker private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024; // 12 MB 35*90c8c64dSAndroid Build Coastguard Worker private static final int MAX_ART_WIDTH = 800; // pixels 36*90c8c64dSAndroid Build Coastguard Worker private static final int MAX_ART_HEIGHT = 480; // pixels 37*90c8c64dSAndroid Build Coastguard Worker 38*90c8c64dSAndroid Build Coastguard Worker // Resolution reasonable for carrying around as an icon (generally in 39*90c8c64dSAndroid Build Coastguard Worker // MediaDescription.getIconBitmap). This should not be bigger than necessary, because 40*90c8c64dSAndroid Build Coastguard Worker // the MediaDescription object should be lightweight. If you set it too high and try to 41*90c8c64dSAndroid Build Coastguard Worker // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors. 42*90c8c64dSAndroid Build Coastguard Worker private static final int MAX_ART_WIDTH_ICON = 128; // pixels 43*90c8c64dSAndroid Build Coastguard Worker private static final int MAX_ART_HEIGHT_ICON = 128; // pixels 44*90c8c64dSAndroid Build Coastguard Worker 45*90c8c64dSAndroid Build Coastguard Worker private static final int BIG_BITMAP_INDEX = 0; 46*90c8c64dSAndroid Build Coastguard Worker private static final int ICON_BITMAP_INDEX = 1; 47*90c8c64dSAndroid Build Coastguard Worker 48*90c8c64dSAndroid Build Coastguard Worker private final LruCache<String, Bitmap[]> mCache; 49*90c8c64dSAndroid Build Coastguard Worker 50*90c8c64dSAndroid Build Coastguard Worker private static final AlbumArtCache sInstance = new AlbumArtCache(); 51*90c8c64dSAndroid Build Coastguard Worker getInstance()52*90c8c64dSAndroid Build Coastguard Worker public static AlbumArtCache getInstance() { 53*90c8c64dSAndroid Build Coastguard Worker return sInstance; 54*90c8c64dSAndroid Build Coastguard Worker } 55*90c8c64dSAndroid Build Coastguard Worker AlbumArtCache()56*90c8c64dSAndroid Build Coastguard Worker private AlbumArtCache() { 57*90c8c64dSAndroid Build Coastguard Worker // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and 58*90c8c64dSAndroid Build Coastguard Worker // Integer.MAX_VALUE: 59*90c8c64dSAndroid Build Coastguard Worker int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE, 60*90c8c64dSAndroid Build Coastguard Worker (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()/4))); 61*90c8c64dSAndroid Build Coastguard Worker mCache = new LruCache<String, Bitmap[]>(maxSize) { 62*90c8c64dSAndroid Build Coastguard Worker @Override 63*90c8c64dSAndroid Build Coastguard Worker protected int sizeOf(String key, Bitmap[] value) { 64*90c8c64dSAndroid Build Coastguard Worker return value[BIG_BITMAP_INDEX].getByteCount() 65*90c8c64dSAndroid Build Coastguard Worker + value[ICON_BITMAP_INDEX].getByteCount(); 66*90c8c64dSAndroid Build Coastguard Worker } 67*90c8c64dSAndroid Build Coastguard Worker }; 68*90c8c64dSAndroid Build Coastguard Worker } 69*90c8c64dSAndroid Build Coastguard Worker getBigImage(String artUrl)70*90c8c64dSAndroid Build Coastguard Worker public Bitmap getBigImage(String artUrl) { 71*90c8c64dSAndroid Build Coastguard Worker Bitmap[] result = mCache.get(artUrl); 72*90c8c64dSAndroid Build Coastguard Worker return result == null ? null : result[BIG_BITMAP_INDEX]; 73*90c8c64dSAndroid Build Coastguard Worker } 74*90c8c64dSAndroid Build Coastguard Worker getIconImage(String artUrl)75*90c8c64dSAndroid Build Coastguard Worker public Bitmap getIconImage(String artUrl) { 76*90c8c64dSAndroid Build Coastguard Worker Bitmap[] result = mCache.get(artUrl); 77*90c8c64dSAndroid Build Coastguard Worker return result == null ? null : result[ICON_BITMAP_INDEX]; 78*90c8c64dSAndroid Build Coastguard Worker } 79*90c8c64dSAndroid Build Coastguard Worker fetch(final String artUrl, final FetchListener listener)80*90c8c64dSAndroid Build Coastguard Worker public void fetch(final String artUrl, final FetchListener listener) { 81*90c8c64dSAndroid Build Coastguard Worker // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests 82*90c8c64dSAndroid Build Coastguard Worker // are not handled properly: they may cause redundant costly operations, like HTTP 83*90c8c64dSAndroid Build Coastguard Worker // requests and bitmap rescales. For production-level apps, we recommend you use 84*90c8c64dSAndroid Build Coastguard Worker // a proper image loading library, like Glide. 85*90c8c64dSAndroid Build Coastguard Worker Bitmap[] bitmap = mCache.get(artUrl); 86*90c8c64dSAndroid Build Coastguard Worker if (bitmap != null) { 87*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "getOrFetch: album art is in cache, using it", artUrl); 88*90c8c64dSAndroid Build Coastguard Worker listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]); 89*90c8c64dSAndroid Build Coastguard Worker return; 90*90c8c64dSAndroid Build Coastguard Worker } 91*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "getOrFetch: starting asynctask to fetch ", artUrl); 92*90c8c64dSAndroid Build Coastguard Worker 93*90c8c64dSAndroid Build Coastguard Worker new AsyncTask<Void, Void, Bitmap[]>() { 94*90c8c64dSAndroid Build Coastguard Worker @Override 95*90c8c64dSAndroid Build Coastguard Worker protected Bitmap[] doInBackground(Void[] objects) { 96*90c8c64dSAndroid Build Coastguard Worker Bitmap[] bitmaps; 97*90c8c64dSAndroid Build Coastguard Worker try { 98*90c8c64dSAndroid Build Coastguard Worker Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap(artUrl, 99*90c8c64dSAndroid Build Coastguard Worker MAX_ART_WIDTH, MAX_ART_HEIGHT); 100*90c8c64dSAndroid Build Coastguard Worker Bitmap icon = BitmapHelper.scaleBitmap(bitmap, 101*90c8c64dSAndroid Build Coastguard Worker MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON); 102*90c8c64dSAndroid Build Coastguard Worker bitmaps = new Bitmap[] {bitmap, icon}; 103*90c8c64dSAndroid Build Coastguard Worker mCache.put(artUrl, bitmaps); 104*90c8c64dSAndroid Build Coastguard Worker } catch (IOException e) { 105*90c8c64dSAndroid Build Coastguard Worker return null; 106*90c8c64dSAndroid Build Coastguard Worker } 107*90c8c64dSAndroid Build Coastguard Worker LogHelper.d(TAG, "doInBackground: putting bitmap in cache. cache size=" + 108*90c8c64dSAndroid Build Coastguard Worker mCache.size()); 109*90c8c64dSAndroid Build Coastguard Worker return bitmaps; 110*90c8c64dSAndroid Build Coastguard Worker } 111*90c8c64dSAndroid Build Coastguard Worker 112*90c8c64dSAndroid Build Coastguard Worker @Override 113*90c8c64dSAndroid Build Coastguard Worker protected void onPostExecute(Bitmap[] bitmaps) { 114*90c8c64dSAndroid Build Coastguard Worker if (bitmaps == null) { 115*90c8c64dSAndroid Build Coastguard Worker listener.onError(artUrl, new IllegalArgumentException("got null bitmaps")); 116*90c8c64dSAndroid Build Coastguard Worker } else { 117*90c8c64dSAndroid Build Coastguard Worker listener.onFetched(artUrl, 118*90c8c64dSAndroid Build Coastguard Worker bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]); 119*90c8c64dSAndroid Build Coastguard Worker } 120*90c8c64dSAndroid Build Coastguard Worker } 121*90c8c64dSAndroid Build Coastguard Worker }.execute(); 122*90c8c64dSAndroid Build Coastguard Worker } 123*90c8c64dSAndroid Build Coastguard Worker 124*90c8c64dSAndroid Build Coastguard Worker public static abstract class FetchListener { onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage)125*90c8c64dSAndroid Build Coastguard Worker public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage); onError(String artUrl, Exception e)126*90c8c64dSAndroid Build Coastguard Worker public void onError(String artUrl, Exception e) { 127*90c8c64dSAndroid Build Coastguard Worker LogHelper.e(TAG, e, "AlbumArtFetchListener: error while downloading " + artUrl); 128*90c8c64dSAndroid Build Coastguard Worker } 129*90c8c64dSAndroid Build Coastguard Worker } 130*90c8c64dSAndroid Build Coastguard Worker } 131