incutonez incutonez - 2 months ago 25
Android Question

Overwrite or update Android playlist database from Cursor

I'm writing a small app that retrieves all items in an Android created playlist, with the ability to sort the list by a certain order. What I want to do is have this sort actually save back to the database, or update the playlist file. Being relatively new to Android development, I'm not sure what the proper approach is to this.

Here's my code:

import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Spinner;

import java.util.List;

public class PlaylistSongActivity extends AppCompatActivity {
final String SONG_ID_KEY = MediaStore.Audio.AudioColumns._ID;
final String SONG_ARTIST_KEY = MediaStore.Audio.AudioColumns.ARTIST;
final String SONG_ALBUM_KEY = MediaStore.Audio.AudioColumns.ALBUM;
final String SONG_TITLE_KEY = MediaStore.Audio.AudioColumns.TITLE;
final String SONG_TRACK_NO_KEY = MediaStore.Audio.AudioColumns.TRACK;
final String CASE_INSENTIVE = " COLLATE NOCASE ";
final String[] PLAYLIST_QUERY_COLUMNS = {SONG_ID_KEY, SONG_ARTIST_KEY, SONG_TITLE_KEY, SONG_ALBUM_KEY, SONG_TRACK_NO_KEY};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playlist);
// Spinner values
final Spinner playlistSettingsDropdown = (Spinner) findViewById(R.id.playlistSettingsDropdown);
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.playlist_settings_array, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
playlistSettingsDropdown.setAdapter(adapter);
playlistSettingsDropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
sortGrid(pos);
}

public void setId(int pos) {
playlistSettingsDropdown.setSelection(pos);
}

public void onNothingSelected(AdapterView<?> parent) {
// Another interface callback
}
});

// Song grid
ListView playlistSongsView = getSongGrid();
final Cursor playlistSongs = createPlaylistCursor(null);
PlaylistSongCursor playlistSongsAdapter = new PlaylistSongCursor(playlistSongsView.getContext(), playlistSongs);
playlistSongsView.setAdapter((playlistSongsAdapter));
}

public void sortGrid(int sortType) {
ListView songGrid = getSongGrid();
PlaylistSongCursor songGridAdapter = (PlaylistSongCursor) songGrid.getAdapter();
String sort = SONG_ARTIST_KEY + CASE_INSENTIVE + " ASC," +
SONG_ALBUM_KEY + CASE_INSENTIVE + " ASC," +
SONG_TRACK_NO_KEY + " ASC";
switch (sortType) {
case 1:
sort = SONG_ARTIST_KEY + CASE_INSENTIVE + " DESC," +
SONG_ALBUM_KEY + CASE_INSENTIVE + " DESC," +
SONG_TRACK_NO_KEY + " DESC";
}
songGridAdapter.changeCursor(createPlaylistCursor(sort));
}

public ListView getSongGrid() {
return (ListView) findViewById((R.id.playlistItems));
}

public Uri getPlaylistUri() {
int playlistId = getIntent().getExtras().getInt("playlistId");
return MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
}

public Cursor createPlaylistCursor(String sort) {
return getContentResolver().query(getPlaylistUri(), null, null, null, sort);
}
}


So I get the list's ID from my passed intent, grab its URI from the
MediaStore
package, and then use the URI to get the Cursor from the query using
ContentProvider
... this all works, the playlist songs show up just fine, and my sorting works as well (although, I'd welcome any critique on what I'm doing). The problem is how would I use the cursor to save back to the database, so it persists to other music apps? (BlackPlayer has this ability with its Sort by Artist - Album option)

I was thinking of a few ways, but I'm not entirely sure how to execute them:


  • Do something with
    ContentValues
    (retrieved from
    DatabaseUtils.cursorRowToContentValues
    )

  • Use
    getWritableDatabase
    somehow... not sure how to retrieve the DB itself

  • Overwrite the
    File
    , but because it's a
    URI
    and
    SQLite
    file, I wasn't sure how to save a
    File
    like this... does it have to be in a special format?



Does anyone have any insight?

UPDATE

I started going down the route of using bulkInsert, but it appears it doesn't insert the
ContentValues[]
into my DB... this definitely deletes my rows, and my
retVal
contains the number of items to insert, it just doesn't insert...

myButton.setOnClickListener(new AdapterView.OnClickListener() {
public void onClick(View view) {
ListView songGrid = getSongGrid();
PlaylistSongCursor songGridAdapter = (PlaylistSongCursor) songGrid.getAdapter();
Cursor myCursor = songGridAdapter.getCursor();
ContentValues[] retVal = new ContentValues[myCursor.getCount()];
ContentValues map;
int i = 0;
if(myCursor.moveToFirst()) {
do {
map = new ContentValues();
DatabaseUtils.cursorRowToContentValues(myCursor, map);
retVal[i++] = map;
} while(myCursor.moveToNext());
}
// Empties db
getContentResolver().delete(getPlaylistUri(), null, null);
// Logs 2 (as that's how many items are in my list)
Log.d("BLA", String.valueOf(retVal.length));
final int retInt = getContentResolver().bulkInsert(getPlaylistUri(), retVal);
// Logs 0
Log.d("DONE", String.valueOf(retInt));
}
});

Answer

With an incredible amount of help from qbeck, I was able to figure this one out. I was missing some key things... in order to save back to the playlist's Uri, I had to make sure it used the same columns its schema has... looking at the MediaStore.Audio.Playlists.Members.* attributes, I could sort of glean what the table's schema's actual fields were... Android Studio made these keys in bold, but looking back on it now, I think that's what this page is telling me.

I used this information to figure out that I needed to update the PLAY_ORDER value, so I first started down the route of ContentProvider.update, but for each row, this took quite some time for large lists, and I reached some sort of Cursor undefined error. I then tried deleting all rows in the DB and doing a ContentProvider.bulkInsert, which was quite a bit quicker AND did what I needed it to do... now my lists appear in this sorted order in other music apps.

getSaveButton().setOnClickListener(new AdapterView.OnClickListener() {
    public void onClick(View view) {
        Cursor myCursor = createPlaylistCursor(sort);
        int i = 0;
        if(myCursor.moveToFirst()) {
            Uri playListUri = getPlaylistUri();
            ContentValues[] values = new ContentValues[myCursor.getCount()];
            ContentResolver contentResolver = getContentResolver();
            do {
                ContentValues map = new ContentValues();
                map.put(PLAY_ORDER_KEY, Long.valueOf(i + 1));
                map.put(AUDIO_ID_KEY, myCursor.getInt(myCursor.getColumnIndexOrThrow(AUDIO_ID_KEY)));
                values[i++] = map;
            } while(myCursor.moveToNext());
            contentResolver.delete(playListUri, null, null);
            contentResolver.bulkInsert(playListUri, values);
            Log.d("BLA", "done");
        }
    }
});