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