diff options
-rw-r--r-- | src/com/cyngn/eleven/loaders/PlaylistSongLoader.java | 148 | ||||
-rw-r--r-- | src/com/cyngn/eleven/utils/MusicUtils.java | 25 |
2 files changed, 160 insertions, 13 deletions
diff --git a/src/com/cyngn/eleven/loaders/PlaylistSongLoader.java b/src/com/cyngn/eleven/loaders/PlaylistSongLoader.java index eba3c8f..13dd9be 100644 --- a/src/com/cyngn/eleven/loaders/PlaylistSongLoader.java +++ b/src/com/cyngn/eleven/loaders/PlaylistSongLoader.java @@ -11,10 +11,16 @@ package com.cyngn.eleven.loaders; +import android.content.ContentProviderOperation; import android.content.Context; +import android.content.OperationApplicationException; import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; import android.provider.MediaStore; import android.provider.MediaStore.Audio.AudioColumns; +import android.provider.MediaStore.Audio.Playlists; +import android.util.Log; import com.cyngn.eleven.model.Song; import com.cyngn.eleven.utils.Lists; @@ -25,10 +31,11 @@ import java.util.List; /** * Used to query {@link MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI} and * return the songs for a particular playlist. - * + * * @author Andrew Neal (andrewdneal@gmail.com) */ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> { + private static final String TAG = PlaylistSongLoader.class.getSimpleName(); /** * The result @@ -43,15 +50,15 @@ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> { /** * The Id of the playlist the songs belong to. */ - private final Long mPlaylistID; + private final long mPlaylistID; /** * Constructor of <code>SongLoader</code> - * - * @param context The {@link Context} to use - * @param playlistID The Id of the playlist the songs belong to. + * + * @param context The {@link Context} to use + * @param playlistId The Id of the playlist the songs belong to. */ - public PlaylistSongLoader(final Context context, final Long playlistId) { + public PlaylistSongLoader(final Context context, final long playlistId) { super(context); mPlaylistID = playlistId; } @@ -61,8 +68,55 @@ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> { */ @Override public List<Song> loadInBackground() { + final int playlistCount = countPlaylist(getContext(), mPlaylistID); + // Create the Cursor mCursor = makePlaylistSongCursor(getContext(), mPlaylistID); + + if (mCursor != null) { + boolean runCleanup = false; + + // if the raw playlist count differs from the mapped playlist count (ie the raw mapping + // table vs the mapping table join the audio table) that means the playlist mapping table + // is messed up + if (mCursor.getCount() != playlistCount) { + Log.w(TAG, "Count Differs - raw is: " + playlistCount + " while cursor is " + + mCursor.getCount()); + + runCleanup = true; + } + + // check if the play order is already messed up by duplicates + if (!runCleanup && mCursor.moveToFirst()) { + final int playOrderCol = mCursor.getColumnIndexOrThrow(Playlists.Members.PLAY_ORDER); + + int lastPlayOrder = -1; + do { + int playOrder = mCursor.getInt(playOrderCol); + // if we have duplicate play orders, we need to recreate the playlist + if (playOrder == lastPlayOrder) { + runCleanup = true; + break; + } + lastPlayOrder = playOrder; + } while (mCursor.moveToNext()); + } + + if (runCleanup) { + Log.w(TAG, "Playlist order has flaws - recreating playlist"); + + // cleanup the playlist + cleanupPlaylist(getContext(), mPlaylistID, mCursor); + + // create a new cursor + mCursor.close(); + mCursor = makePlaylistSongCursor(getContext(), mPlaylistID); + if (mCursor != null) { + Log.d(TAG, "New Count is: " + mCursor.getCount()); + } + } + } + // Gather the data if (mCursor != null && mCursor.moveToFirst()) { do { @@ -113,6 +167,86 @@ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> { } /** + * Cleans up the playlist based on the passed in cursor's data + * @param context The {@link Context} to use + * @param playlistId playlistId to clean up + * @param cursor data to repopulate the playlist with + */ + private static void cleanupPlaylist(final Context context, final long playlistId, + final Cursor cursor) { + Log.w(TAG, "Cleaning up playlist: " + playlistId); + + final int idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Playlists.Members.AUDIO_ID); + final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId); + + ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>(); + + // Delete all results in the playlist + ops.add(ContentProviderOperation.newDelete(uri).build()); + + // yield the db every 100 records to prevent ANRs + final int YIELD_FREQUENCY = 100; + + // for each item, reset the play order position + if (cursor.moveToFirst() && cursor.getCount() > 0) { + do { + final ContentProviderOperation.Builder builder = + ContentProviderOperation.newInsert(uri) + .withValue(Playlists.Members.PLAY_ORDER, cursor.getPosition()) + .withValue(Playlists.Members.AUDIO_ID, cursor.getLong(idCol)); + + // yield at the end and not at 0 by incrementing by 1 + if ((cursor.getPosition() + 1) % YIELD_FREQUENCY == 0) { + builder.withYieldAllowed(true); + } + ops.add(builder.build()); + } while (cursor.moveToNext()); + } + + try { + // run the batch operation + context.getContentResolver().applyBatch(MediaStore.AUTHORITY, ops); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException " + e + " while cleaning up playlist " + playlistId); + } catch (OperationApplicationException e) { + Log.e(TAG, "OperationApplicationException " + e + " while cleaning up playlist " + + playlistId); + } + } + + /** + * Returns the playlist count for the raw playlist mapping table + * @param context The {@link Context} to use + * @param playlistId playlistId to count + * @return the number of tracks in the raw playlist mapping table + */ + private static int countPlaylist(final Context context, final long playlistId) { + Cursor c = null; + try { + // when we query using only the audio_id column we will get the raw mapping table + // results - which will tell us if the table has rows that don't exist in the normal + // table + c = context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{ + MediaStore.Audio.Playlists.Members.AUDIO_ID, + }, null, null, + MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); + + if (c != null) { + return c.getCount(); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + return 0; + } + + /** * Creates the {@link Cursor} used to run the query. * * @param context The {@link Context} to use. @@ -142,6 +276,8 @@ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> { AudioColumns.DURATION, /* 7 */ AudioColumns.YEAR, + /* 8 */ + Playlists.Members.PLAY_ORDER, }, mSelection.toString(), null, MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); } diff --git a/src/com/cyngn/eleven/utils/MusicUtils.java b/src/com/cyngn/eleven/utils/MusicUtils.java index e100e87..d3c76bf 100644 --- a/src/com/cyngn/eleven/utils/MusicUtils.java +++ b/src/com/cyngn/eleven/utils/MusicUtils.java @@ -1086,14 +1086,25 @@ public final class MusicUtils { final int size = ids.length; final ContentResolver resolver = context.getContentResolver(); final String[] projection = new String[] { - "count(*)" + "max(" + Playlists.Members.PLAY_ORDER + ")", }; final Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistid); - Cursor cursor = resolver.query(uri, projection, null, null, null); - cursor.moveToFirst(); - final int base = cursor.getInt(0); - cursor.close(); - cursor = null; + Cursor cursor = null; + int base = 0; + + try { + cursor = resolver.query(uri, projection, null, null, null); + + if (cursor != null && cursor.moveToFirst()) { + base = cursor.getInt(0) + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + cursor = null; + } + } + int numinserted = 0; for (int offSet = 0; offSet < size; offSet += 1000) { makeInsertItems(ids, offSet, 1000, base); @@ -1136,7 +1147,7 @@ public final class MusicUtils { try { mService.enqueue(list, MusicPlaybackService.LAST, sourceId, sourceType.mId); final String message = makeLabel(context, R.plurals.NNNtrackstoqueue, list.length); - CustomToast.makeText((Activity)context, message,CustomToast.LENGTH_SHORT).show(); + CustomToast.makeText((Activity) context, message, CustomToast.LENGTH_SHORT).show(); } catch (final RemoteException ignored) { } } |