summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/cyngn/eleven/loaders/PlaylistSongLoader.java148
-rw-r--r--src/com/cyngn/eleven/utils/MusicUtils.java25
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) {
}
}