Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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
Loading
Loading
Loading
Loading
+142 −6
Original line number Diff line number Diff line
@@ -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;
@@ -29,6 +35,7 @@ import java.util.List;
 * @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 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);
    }
+18 −7
Original line number Diff line number Diff line
@@ -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 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);