Commit 1f86c572 authored by linus_lee's avatar linus_lee
Browse files

Eleven: Fix playlist reordering not working after awhile

Still trying to find the root cause (if it is Eleven or external)
but this will find the errors and fix it after the fact

https://cyanogen.atlassian.net/browse/MUSIC-179

Change-Id: Iaa3e27522aefc656f37ff7e727e9129c1a07f12e
parent 8274981a
......@@ -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 {
......@@ -112,6 +166,86 @@ public class PlaylistSongLoader extends WrappedAsyncTaskLoader<List<Song>> {
return mSongList;
}
/**
* 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.
*
......@@ -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);
}
......
......@@ -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) {
}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment