OSDN Git Service

c962d45476d5229c71b9720b28ed1d6fc99ebbcf
[android-x86/packages-apps-Eleven.git] / src / org / lineageos / eleven / cache / PlaylistWorkerTask.java
1 /*
2 * Copyright (C) 2014 The CyanogenMod Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.lineageos.eleven.cache;
17
18 import android.content.Context;
19 import android.database.Cursor;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Rect;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.TransitionDrawable;
25 import android.provider.MediaStore;
26 import android.widget.ImageView;
27
28 import org.lineageos.eleven.cache.ImageWorker.ImageType;
29 import org.lineageos.eleven.loaders.PlaylistSongLoader;
30 import org.lineageos.eleven.loaders.SortedCursor;
31 import org.lineageos.eleven.provider.PlaylistArtworkStore;
32 import org.lineageos.eleven.provider.SongPlayCount;
33
34 import java.util.ArrayList;
35 import java.util.HashSet;
36
37 /**
38  * The playlistWorkerTask will load either the top artist image or the cover art (a combination of
39  * up to 4 of the top song's album images) into the designated ImageView.  If not enough time has
40  * elapsed since the last update or if the # of songs in the playlist hasn't changed, no new images
41  * will be loaded.
42  */
43 public class PlaylistWorkerTask extends BitmapWorkerTask<Void, Void, TransitionDrawable> {
44     // the work type
45     public enum PlaylistWorkerType {
46         Artist, CoverArt
47     }
48
49     // number of images to load in the cover art
50     private static final int MAX_NUM_BITMAPS_TO_LOAD = 4;
51
52     protected final long mPlaylistId;
53     protected final PlaylistArtworkStore mPlaylistStore;
54     protected final PlaylistWorkerType mWorkerType;
55
56     // if we've found it in the cache, don't do any more logic unless enough time has elapsed or
57     // if the playlist has changed
58     protected final boolean mFoundInCache;
59
60     // because a cached image can be loaded, we use this flag to signal to remove that default image
61     protected boolean mFallbackToDefaultImage;
62
63     /**
64      * Constructor of <code>PlaylistWorkerTask</code>
65      * @param key the key of the image to store to
66      * @param playlistId the playlist identifier
67      * @param type Artist or CoverArt?
68      * @param foundInCache does this exist in the memory cache already
69      * @param imageView The {@link ImageView} to use.
70      * @param fromDrawable what drawable to transition from
71      */
72     public PlaylistWorkerTask(final String key, final long playlistId, final PlaylistWorkerType type,
73                               final boolean foundInCache, final ImageView imageView,
74                               final Drawable fromDrawable, final Context context) {
75         super(key, imageView, ImageType.PLAYLIST, fromDrawable, context);
76
77         mPlaylistId = playlistId;
78         mWorkerType = type;
79         mPlaylistStore = PlaylistArtworkStore.getInstance(mContext);
80         mFoundInCache = foundInCache;
81         mFallbackToDefaultImage = false;
82     }
83
84     /**
85      * {@inheritDoc}
86      */
87     @Override
88     protected TransitionDrawable doInBackground(final Void... params) {
89         if (isCancelled()) {
90             return null;
91         }
92
93         Bitmap bitmap = null;
94
95         // See if we need to update the image
96         boolean needsUpdate = false;
97         if (mWorkerType == PlaylistWorkerType.Artist
98                 && mPlaylistStore.needsArtistArtUpdate(mPlaylistId)) {
99             needsUpdate = true;
100         } else if (mWorkerType == PlaylistWorkerType.CoverArt
101                 && mPlaylistStore.needsCoverArtUpdate(mPlaylistId)) {
102             needsUpdate = true;
103         }
104
105         // if we don't need to update and we've already found it in the cache, then return
106         if (!needsUpdate && mFoundInCache) {
107             return null;
108         }
109
110         // if we didn't find it in memory cache, try the disk cache
111         if (!mFoundInCache) {
112             bitmap = mImageCache.getCachedBitmap(mKey);
113         }
114
115         // if we don't need an update, return something
116         if (!needsUpdate) {
117             if (bitmap != null) {
118                 // if we found a bitmap, return it
119                 return createImageTransitionDrawable(bitmap);
120             } else {
121                 // otherwise return null since we don't need an update
122                 return null;
123             }
124         }
125
126         // otherwise re-run the logic to get the bitmap
127         try (Cursor sortedCursor = getTopSongsForPlaylist()) {
128             // get the top songs for our playlist
129
130             if (isCancelled()) {
131                 return null;
132             }
133
134             if (sortedCursor == null || sortedCursor.getCount() == 0) {
135                 // if all songs were removed from the playlist, update the last updated time
136                 // and reset to the default art
137                 if (mWorkerType == PlaylistWorkerType.Artist) {
138                     // update the timestamp
139                     mPlaylistStore.updateArtistArt(mPlaylistId);
140                     // remove the cached image
141                     mImageCache.removeFromCache(PlaylistArtworkStore.getArtistCacheKey(mPlaylistId));
142                     // revert back to default image
143                     mFallbackToDefaultImage = true;
144                 } else if (mWorkerType == PlaylistWorkerType.CoverArt) {
145                     // update the timestamp
146                     mPlaylistStore.updateCoverArt(mPlaylistId);
147                     // remove the cached image
148                     mImageCache.removeFromCache(PlaylistArtworkStore.getCoverCacheKey(mPlaylistId));
149                     // revert back to default image
150                     mFallbackToDefaultImage = true;
151                 }
152             } else if (mWorkerType == PlaylistWorkerType.Artist) {
153                 bitmap = loadTopArtist(sortedCursor);
154             } else {
155                 bitmap = loadTopSongs(sortedCursor);
156             }
157         }
158
159         // if we have a bitmap create a transition drawable
160         if (bitmap != null) {
161             return createImageTransitionDrawable(bitmap);
162         }
163
164         return null;
165     }
166
167     /**
168      * This gets the sorted cursor of the songs from a playlist based on play count
169      * @return Cursor containing the sorted list
170      */
171     protected Cursor getTopSongsForPlaylist() {
172         Cursor playlistCursor = null;
173         SortedCursor sortedCursor = null;
174
175         try {
176             // gets the songs in the playlist
177             playlistCursor = PlaylistSongLoader.makePlaylistSongCursor(mContext, mPlaylistId);
178             if (playlistCursor == null || !playlistCursor.moveToFirst()) {
179                 return null;
180             }
181
182             // get all the ids in the list
183             long[] songIds = new long[playlistCursor.getCount()];
184             do {
185                 long id = playlistCursor.getLong(playlistCursor.getColumnIndex(
186                         MediaStore.Audio.Playlists.Members.AUDIO_ID));
187
188                 songIds[playlistCursor.getPosition()] = id;
189             } while (playlistCursor.moveToNext());
190
191             if (isCancelled()) {
192                 return null;
193             }
194
195             // find the sorted order for the playlist based on the top songs database
196             long[] order = SongPlayCount.getInstance(mContext).getTopPlayedResultsForList(songIds);
197
198             // create a new cursor that takes the playlist cursor and the sorted order
199             sortedCursor = new SortedCursor(playlistCursor, order,
200                     MediaStore.Audio.Playlists.Members.AUDIO_ID, null);
201
202             // since this cursor is now wrapped by SortedTracksCursor, remove the reference here
203             // so we don't accidentally close it in the finally loop
204             playlistCursor = null;
205         } finally {
206             // if we quit early from isCancelled(), close our cursor
207             if (playlistCursor != null) {
208                 playlistCursor.close();
209                 playlistCursor = null;
210             }
211         }
212
213         return sortedCursor;
214     }
215
216     /**
217      * Gets the most played song's artist image
218      * @param sortedCursor the sorted playlist song cursor
219      * @return Bitmap of the artist
220      */
221     protected Bitmap loadTopArtist(Cursor sortedCursor) {
222         if (sortedCursor == null || !sortedCursor.moveToFirst()) {
223             return null;
224         }
225
226         Bitmap bitmap = null;
227         int artistIndex = sortedCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST);
228         String artistName = null;
229
230         do {
231             if (isCancelled()) {
232                 return null;
233             }
234
235             artistName = sortedCursor.getString(artistIndex);
236             // try to load the bitmap
237             bitmap = ImageWorker.getBitmapInBackground(mContext, mImageCache, artistName,
238                     null, artistName, -1, ImageType.ARTIST);
239         } while (sortedCursor.moveToNext() && bitmap == null);
240
241         if (bitmap == null) {
242             // if we can't find any artist images, try loading the top songs image
243             bitmap = mImageCache.getCachedBitmap(
244                     PlaylistArtworkStore.getCoverCacheKey(mPlaylistId));
245         }
246
247         if (bitmap != null) {
248             // add the image to the cache
249             mImageCache.addBitmapToCache(mKey, bitmap, true);
250         } else {
251             mImageCache.removeFromCache(mKey);
252             mFallbackToDefaultImage = true;
253         }
254
255         // store the fact that we ran this code into the db to prevent multiple re-runs
256         mPlaylistStore.updateArtistArt(mPlaylistId);
257
258         return bitmap;
259     }
260
261     /**
262      * Gets the Cover Art of the playlist, which is a combination of the top song's album image
263      * @param sortedCursor the sorted playlist song cursor
264      * @return Bitmap of the artist
265      */
266     protected Bitmap loadTopSongs(Cursor sortedCursor) {
267         if (sortedCursor == null || !sortedCursor.moveToFirst()) {
268             return null;
269         }
270
271         ArrayList<Bitmap> loadedBitmaps = new ArrayList<>(MAX_NUM_BITMAPS_TO_LOAD);
272
273         final int artistIdx = sortedCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ARTIST);
274         final int albumIdIdx = sortedCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM_ID);
275         final int albumIdx = sortedCursor.getColumnIndex(MediaStore.Audio.AudioColumns.ALBUM);
276
277         Bitmap bitmap = null;
278         String artistName = null;
279         String albumName = null;
280         long albumId = -1;
281
282         // create a hashset of the keys so we don't load images from the same album multiple times
283         HashSet<String> keys = new HashSet<>(sortedCursor.getCount());
284
285         do {
286             if (isCancelled()) {
287                 return null;
288             }
289
290             artistName = sortedCursor.getString(artistIdx);
291             albumName = sortedCursor.getString(albumIdx);
292             albumId = sortedCursor.getLong(albumIdIdx);
293
294             String key = ImageFetcher.generateAlbumCacheKey(albumName, artistName);
295
296             // if we successfully added the key (ie the key didn't previously exist)
297             if (keys.add(key)) {
298                 // try to load the bitmap
299                 bitmap = ImageWorker.getBitmapInBackground(mContext, mImageCache,
300                         key, albumName, artistName, albumId, ImageType.ALBUM);
301
302                 // if we got the bitmap, add it to the list
303                 if (bitmap != null) {
304                     loadedBitmaps.add(bitmap);
305                     bitmap = null;
306                 }
307             }
308         } while (sortedCursor.moveToNext() && loadedBitmaps.size() < MAX_NUM_BITMAPS_TO_LOAD);
309
310         // if we found at least 1 bitmap
311         if (loadedBitmaps.size() > 0) {
312             // get the first bitmap
313             bitmap = loadedBitmaps.get(0);
314
315             // if we have many bitmaps
316             if (loadedBitmaps.size() == MAX_NUM_BITMAPS_TO_LOAD) {
317                 // create a combined bitmap of the 4 images
318                 final int width = bitmap.getWidth();
319                 final int height = bitmap.getHeight();
320                 Bitmap combinedBitmap = Bitmap.createBitmap(width, height,
321                         bitmap.getConfig());
322                 Canvas combinedCanvas = new Canvas(combinedBitmap);
323
324                 // top left
325                 combinedCanvas.drawBitmap(loadedBitmaps.get(0), null,
326                         new Rect(0, 0, width / 2, height / 2), null);
327
328                 // top right
329                 combinedCanvas.drawBitmap(loadedBitmaps.get(1), null,
330                         new Rect(width / 2, 0, width, height / 2), null);
331
332                 // bottom left
333                 combinedCanvas.drawBitmap(loadedBitmaps.get(2), null,
334                         new Rect(0, height / 2, width / 2, height), null);
335
336                 // bottom right
337                 combinedCanvas.drawBitmap(loadedBitmaps.get(3), null,
338                         new Rect(width / 2, height / 2, width, height), null);
339
340                 combinedCanvas = null;
341                 bitmap = combinedBitmap;
342             }
343         }
344
345         // store the fact that we ran this code into the db to prevent multiple re-runs
346         mPlaylistStore.updateCoverArt(mPlaylistId);
347
348         if (bitmap != null) {
349             // add the image to the cache
350             mImageCache.addBitmapToCache(mKey, bitmap, true);
351         } else {
352             mImageCache.removeFromCache(mKey);
353             mFallbackToDefaultImage = true;
354         }
355
356         return bitmap;
357     }
358
359     /**
360      * {@inheritDoc}
361      */
362     @Override
363     protected void onPostExecute(TransitionDrawable transitionDrawable) {
364         final ImageView imageView = getAttachedImageView();
365         if (imageView != null) {
366             if (transitionDrawable != null) {
367                 imageView.setImageDrawable(transitionDrawable);
368             } else if (mFallbackToDefaultImage) {
369                 ImageFetcher.getInstance(mContext).loadDefaultImage(imageView,
370                         ImageType.PLAYLIST, null, String.valueOf(mPlaylistId));
371             }
372         }
373     }
374 }