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