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