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;
21a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.util.Log;
22a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport android.util.LruCache;
23a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
24a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport com.example.android.supportv4.media.utils.BitmapHelper;
25a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
26a907614755847b2630561a1e5949b2b416600d97Sungsoo Limimport java.io.IOException;
27a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
28a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim/**
29a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim * Implements a basic cache of album arts, with async loading support.
30a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim */
31a907614755847b2630561a1e5949b2b416600d97Sungsoo Limpublic final class AlbumArtCache {
32a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final String TAG = "AlbumArtCache";
33a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
34a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int MAX_ALBUM_ART_CACHE_SIZE = 12*1024*1024;  // 12 MB
35a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int MAX_ART_WIDTH = 800;  // pixels
36a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int MAX_ART_HEIGHT = 480;  // pixels
37a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
38a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    // Resolution reasonable for carrying around as an icon (generally in
39a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    // MediaDescription.getIconBitmap). This should not be bigger than necessary, because
40a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    // the MediaDescription object should be lightweight. If you set it too high and try to
41a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    // serialize the MediaDescription, you may get FAILED BINDER TRANSACTION errors.
42a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int MAX_ART_WIDTH_ICON = 128;  // pixels
43a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int MAX_ART_HEIGHT_ICON = 128;  // pixels
44a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
45a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int BIG_BITMAP_INDEX = 0;
46a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final int ICON_BITMAP_INDEX = 1;
47a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
48a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private final LruCache<String, Bitmap[]> mCache;
49a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
50a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private static final AlbumArtCache sInstance = new AlbumArtCache();
51a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
52a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    public static AlbumArtCache getInstance() {
53a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        return sInstance;
54a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
55a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
56a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    private AlbumArtCache() {
57a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // Holds no more than MAX_ALBUM_ART_CACHE_SIZE bytes, bounded by maxmemory/4 and
58a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // Integer.MAX_VALUE:
59a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        int maxSize = Math.min(MAX_ALBUM_ART_CACHE_SIZE,
60a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            (int) (Math.min(Integer.MAX_VALUE, Runtime.getRuntime().maxMemory()/4)));
61a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        mCache = new LruCache<String, Bitmap[]>(maxSize) {
62a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            @Override
63a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            protected int sizeOf(String key, Bitmap[] value) {
64a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                return value[BIG_BITMAP_INDEX].getByteCount()
65a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    + value[ICON_BITMAP_INDEX].getByteCount();
66a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            }
67a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        };
68a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
69a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
70a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    public Bitmap getBigImage(String artUrl) {
71a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        Bitmap[] result = mCache.get(artUrl);
72a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        return result == null ? null : result[BIG_BITMAP_INDEX];
73a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
74a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
75a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    public Bitmap getIconImage(String artUrl) {
76a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        Bitmap[] result = mCache.get(artUrl);
77a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        return result == null ? null : result[ICON_BITMAP_INDEX];
78a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
79a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
80a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    public void fetch(final String artUrl, final FetchListener listener) {
81a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // WARNING: for the sake of simplicity, simultaneous multi-thread fetch requests
82a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // are not handled properly: they may cause redundant costly operations, like HTTP
83a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // requests and bitmap rescales. For production-level apps, we recommend you use
84a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        // a proper image loading library, like Glide.
85a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        Bitmap[] bitmap = mCache.get(artUrl);
86a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        if (bitmap != null) {
87a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            Log.d(TAG, "getOrFetch: album art is in cache, using it " + artUrl);
88a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            listener.onFetched(artUrl, bitmap[BIG_BITMAP_INDEX], bitmap[ICON_BITMAP_INDEX]);
89a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            return;
90a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        }
91a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        Log.d(TAG, "getOrFetch: starting asynctask to fetch " + artUrl);
92a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
93a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        new AsyncTask<Void, Void, Bitmap[]>() {
94a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            @Override
95a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            protected Bitmap[] doInBackground(Void[] objects) {
96a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                Bitmap[] bitmaps;
97a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                try {
98a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    Bitmap bitmap = BitmapHelper.fetchAndRescaleBitmap(artUrl,
99a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                        MAX_ART_WIDTH, MAX_ART_HEIGHT);
100a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    Bitmap icon = BitmapHelper.scaleBitmap(bitmap,
101a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                        MAX_ART_WIDTH_ICON, MAX_ART_HEIGHT_ICON);
102a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    bitmaps = new Bitmap[] {bitmap, icon};
103a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    mCache.put(artUrl, bitmaps);
104a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                } catch (IOException e) {
105a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    return null;
106a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                }
107a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                Log.d(TAG, "doInBackground: putting bitmap in cache. cache size=" + mCache.size());
108a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                return bitmaps;
109a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            }
110a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
111a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            @Override
112a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            protected void onPostExecute(Bitmap[] bitmaps) {
113a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                if (bitmaps == null) {
114a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    listener.onError(artUrl, new IllegalArgumentException("got null bitmaps"));
115a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                } else {
116a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                    listener.onFetched(artUrl,
117a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                        bitmaps[BIG_BITMAP_INDEX], bitmaps[ICON_BITMAP_INDEX]);
118a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim                }
119a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            }
120a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        }.execute();
121a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
122a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim
123a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    public static abstract class FetchListener {
124a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        public abstract void onFetched(String artUrl, Bitmap bigImage, Bitmap iconImage);
125a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        public void onError(String artUrl, Exception e) {
126a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim            Log.e(TAG, "AlbumArtFetchListener: error while downloading " + artUrl, e);
127a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim        }
128a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim    }
129a907614755847b2630561a1e5949b2b416600d97Sungsoo Lim}
130