Add support for playlist playback

No support for creation or editing yet
This commit is contained in:
Christopher Eby 2010-05-16 11:46:04 -05:00
parent 7090c468aa
commit fad3b1160b
10 changed files with 186 additions and 123 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/ic_tab_playlists_selected" />
<item
android:drawable="@drawable/ic_tab_playlists_unselected" />
</selector>

View File

@ -57,6 +57,12 @@
android:layout_height="fill_parent"
android:divider="@null"
android:fastScrollEnabled="true" />
<ListView
android:id="@+id/playlist_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@null"
android:fastScrollEnabled="true" />
</FrameLayout>
</LinearLayout>
</TabHost>

View File

@ -50,6 +50,7 @@
<string name="artists">Artists</string>
<string name="albums">Albums</string>
<string name="songs">Songs</string>
<string name="playlists">Playlists</string>
<string name="none">None</string>

View File

@ -55,16 +55,11 @@ import android.widget.FilterQueryProvider;
*/
public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
/**
* Type indicating that MediaStore.Audio.Artists should be used as the
* provider backing this adapter.
* The type of media represented by this adapter. Must be one of the
* Song.FIELD_* constants. Determines which content provider to query for
* media and what fields to display.
*/
public static final int TYPE_ARTIST = 1;
/**
* Type indicating that MediaStore.Audio.Albums should be used as the
* adapter backing this adapter.
*/
public static final int TYPE_ALBUM = 2;
int mType;
Uri mStore;
String[] mFields;
private String[] mFieldKeys;
@ -73,16 +68,51 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
private CharSequence mConstraint;
/**
* Perform required setup during construction. See constructors for
* details.
* Construct a MediaAdapter representing the given <code>type</code> of
* media.
*
* @param context A Context to use
* @param type The type of media to represent. Must be one of the
* Song.TYPE_* constants. This determines which content provider to query
* and what fields to display in the views.
* @param expandable Whether an expand arrow should be shown to the right
* of the views' text
* @param requery If true, automatically update the adapter when the
* provider backing it changes
*/
private void init(Context context, Uri store, String[] fields, String[] fieldKeys, boolean expandable)
public MediaAdapter(Context context, int type, boolean expandable, boolean requery)
{
mStore = store;
mFields = fields;
mFieldKeys = fieldKeys;
super(context, null, requery);
mType = type;
mExpandable = expandable;
switch (type) {
case Song.TYPE_ARTIST:
mStore = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
mFields = new String[] { MediaStore.Audio.Artists.ARTIST };
mFieldKeys = new String[] { MediaStore.Audio.Artists.ARTIST_KEY };
break;
case Song.TYPE_ALBUM:
mStore = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
mFields = new String[] { MediaStore.Audio.Albums.ARTIST, MediaStore.Audio.Albums.ALBUM };
// Why is there no artist_key column constant in the album MediaStore? The column does seem to exist.
mFieldKeys = new String[] { "artist_key", MediaStore.Audio.Albums.ALBUM_KEY };
break;
case Song.TYPE_SONG:
mStore = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
mFields = new String[] { MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE };
mFieldKeys = new String[] { MediaStore.Audio.Media.ARTIST_KEY, MediaStore.Audio.Media.ALBUM_KEY, MediaStore.Audio.Media.TITLE_KEY };
break;
case Song.TYPE_PLAYLIST:
mStore = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
mFields = new String[] { MediaStore.Audio.Playlists.NAME };
mFieldKeys = null;
break;
default:
throw new IllegalArgumentException("Invalid value for type: " + type);
}
setFilterQueryProvider(this);
requery();
@ -97,65 +127,6 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
}
}
/**
* Construct a MediaAdapter representing an arbitrary media content
* provider.
*
* @param context A Context to use
* @param store The external content uri of the provider
* @param fields The fields to use in the provider. The last field will be
* displayed as the first line in views. If more than one field is given,
* the first field will be displayed as the bottom line.
* @param fieldKeys The sorting keys corresponding to each field from
* <code>fields</code>. Used for filtering.
* @param expandable Whether an expand arrow should be shown to the right
* of the views' text
* @param requery If true, automatically update the adapter when the
* provider backing it changes
*/
protected MediaAdapter(Context context, Uri store, String[] fields, String[] fieldKeys, boolean expandable, boolean requery)
{
super(context, null, requery);
init(context, store, fields, fieldKeys, expandable);
}
/**
* Construct a MediaAdapter representing the given <code>type</code> of
* media.
*
* @param context A Context to use
* @param type The type of media; one of TYPE_ALBUM or TYPE_ARTIST
* @param expandable Whether an expand arrow should be shown to the right
* of the views' text
* @param requery If true, automatically update the adapter when the
* provider backing it changes
*/
public MediaAdapter(Context context, int type, boolean expandable, boolean requery)
{
super(context, null, requery);
Uri store;
String[] fields;
String[] fieldKeys;
switch (type) {
case TYPE_ARTIST:
store = MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI;
fields = new String[] { MediaStore.Audio.Artists.ARTIST };
fieldKeys = new String[] { MediaStore.Audio.Artists.ARTIST_KEY };
break;
case TYPE_ALBUM:
store = MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI;
fields = new String[] { MediaStore.Audio.Albums.ARTIST, MediaStore.Audio.Albums.ALBUM };
// Why is there no artist_key column constant in the album MediaStore? The column does seem to exist.
fieldKeys = new String[] { "artist_key", MediaStore.Audio.Albums.ALBUM_KEY };
break;
default:
throw new IllegalArgumentException("Invalid value for type: " + type);
}
init(context, store, fields, fieldKeys, expandable);
}
public final void requery()
{
changeCursor(runQuery(mConstraint));
@ -174,7 +145,8 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
protected String getSortOrder()
{
return mFieldKeys[mFieldKeys.length - 1];
String[] source = mFieldKeys == null ? mFields : mFieldKeys;
return source[source.length - 1];
}
public Cursor runQuery(CharSequence constraint)
@ -201,11 +173,20 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
}
if (constraint != null && constraint.length() != 0) {
String colKey = MediaStore.Audio.keyFor(constraint.toString());
String spaceColKey = DatabaseUtils.getCollationKey(" ");
String[] colKeys = colKey.split(spaceColKey);
String[] needles;
int size = colKeys.length;
// If we are using sorting keys, we need to change our constraint
// into a list of collation keys. Otherwise, just split the
// constraint with no modification.
if (mFieldKeys != null) {
String colKey = MediaStore.Audio.keyFor(constraint.toString());
String spaceColKey = DatabaseUtils.getCollationKey(" ");
needles = colKey.split(spaceColKey);
} else {
needles = constraint.toString().split("\\s+");
}
int size = needles.length;
if (limiter != null)
++size;
selectionArgs = new String[size];
@ -215,23 +196,23 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
i = 1;
}
String keys = mFieldKeys[0];
for (int j = 1; j != mFieldKeys.length; ++j)
keys += "||" + mFieldKeys[j];
String[] keySource = mFieldKeys == null ? mFields : mFieldKeys;
String keys = keySource[0];
for (int j = 1; j != keySource.length; ++j)
keys += "||" + keySource[j];
for (int j = 0; j != colKeys.length; ++i, ++j) {
selectionArgs[i] = '%' + colKeys[j] + '%';
for (int j = 0; j != needles.length; ++i, ++j) {
selectionArgs[i] = '%' + needles[j] + '%';
if (j != 0 || selection.length() != 0)
if (i != 0)
selection.append(" AND ");
selection.append(keys);
selection.append(" LIKE ?");
selection.append(keys);
selection.append(" LIKE ?");
}
} else if (limiter != null) {
selectionArgs = new String[] { limiter };
} else {
if (limiter != null)
selectionArgs = new String[] { limiter };
else
selectionArgs = null;
selectionArgs = null;
}
String[] projection;
@ -391,6 +372,15 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
return mId;
}
/**
* Returns the type of media contained in the adapter containing this
* view. Will be one of the Song.TYPE_* constants.
*/
public int getMediaType()
{
return mType;
}
public final String getTitle()
{
return mTitle;

View File

@ -45,6 +45,23 @@ public class Song implements Parcelable {
*/
public static final int FLAG_RANDOM = 0x1;
/**
* Type indicating an id represents an artist.
*/
public static final int TYPE_ARTIST = 1;
/**
* Type indicating an id represents an album.
*/
public static final int TYPE_ALBUM = 2;
/**
* Type indicating an id represents a song.
*/
public static final int TYPE_SONG = 3;
/**
* Type indicating an id represents a playlist.
*/
public static final int TYPE_PLAYLIST = 4;
private static final String[] FILLED_PROJECTION = {
MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.DATA,
@ -180,36 +197,77 @@ public class Song implements Parcelable {
}
/**
* Return an array containing all the song ids that match the specified parameters
* Return a cursor containing the ids of all the songs with artist or
* album of the specified id.
*
* @param type Type the id represent. May be 1, 2 or 3, meaning artist,
* album or song, respectively.
* @param id Id of the element
* @param resolver The ContentResolver to run the query with.
* @param type TYPE_ARTIST or TYPE_ALBUM, indicating the the id represents
* an artist or album
* @param id The MediaStore id of the artist or album
*/
public static long[] getAllSongIdsWith(int type, long id)
private static Cursor getMediaCursor(ContentResolver resolver, int type, long id)
{
if (type == 3)
return new long[] { id };
String selection = "=" + id + " AND " + MediaStore.Audio.Media.IS_MUSIC + "!=0";
String selection = '=' + id + " AND " + MediaStore.Audio.Media.IS_MUSIC + "!=0";
switch (type) {
case 2:
selection = MediaStore.Audio.Media.ALBUM_ID + selection;
break;
case 1:
case TYPE_ARTIST:
selection = MediaStore.Audio.Media.ARTIST_ID + selection;
break;
case TYPE_ALBUM:
selection = MediaStore.Audio.Media.ALBUM_ID + selection;
break;
default:
return null;
throw new IllegalArgumentException("Invalid type specified: " + type);
}
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Audio.Media._ID };
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
String sort = MediaStore.Audio.Media.ARTIST_KEY + ',' + MediaStore.Audio.Media.ALBUM_KEY + ',' + MediaStore.Audio.Media.TRACK;
Cursor cursor = resolver.query(media, projection, selection, null, sort);
return resolver.query(media, projection, selection, null, sort);
}
/**
* Return a cursor containing the ids of all the songs in the playlist
* with the given id.
*
* @param resolver The ContentResolver to run the query with.
* @param id The id of the playlist in MediaStore.Audio.Playlists.
*/
private static Cursor getPlaylistCursor(ContentResolver resolver, long id)
{
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", id);
String[] projection = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
return resolver.query(uri, projection, null, null,
MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
}
/**
* Return an array containing all the song ids that match the specified parameters
*
* @param type Type the id represents. Must be one of the Song.TYPE_*
* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
*/
public static long[] getAllSongIdsWith(int type, long id)
{
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
Cursor cursor;
switch (type) {
case TYPE_SONG:
return new long[] { id };
case TYPE_ARTIST:
case TYPE_ALBUM:
cursor = getMediaCursor(resolver, type, id);
break;
case TYPE_PLAYLIST:
cursor = getPlaylistCursor(resolver, id);
break;
default:
throw new IllegalArgumentException("Specified type not valid: " + type);
}
if (cursor == null)
return null;
@ -222,7 +280,7 @@ public class Song implements Parcelable {
for (int i = 0; i != count; ++i) {
if (!cursor.moveToNext())
return null;
songs[i] = cursor.getInt(0);
songs[i] = cursor.getLong(0);
}
cursor.close();
@ -353,8 +411,8 @@ public class Song implements Parcelable {
if (fileDescriptor != null) {
// Construct a MediaScanner
Class mediaScannerClass = Class.forName("android.media.MediaScanner");
Constructor mediaScannerConstructor = mediaScannerClass.getDeclaredConstructor(Context.class);
Class<?> mediaScannerClass = Class.forName("android.media.MediaScanner");
Constructor<?> mediaScannerConstructor = mediaScannerClass.getDeclaredConstructor(Context.class);
Object mediaScanner = mediaScannerConstructor.newInstance(ContextApplication.getContext());
// Call extractAlbumArt(fileDescriptor)

View File

@ -38,11 +38,7 @@ public class SongMediaAdapter extends MediaAdapter {
*/
public SongMediaAdapter(Context context, boolean expandable, boolean requery)
{
super(context,
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[] { MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.ALBUM, MediaStore.Audio.Media.TITLE },
new String[] { MediaStore.Audio.Media.ARTIST_KEY, MediaStore.Audio.Media.ALBUM_KEY, MediaStore.Audio.Media.TITLE_KEY },
expandable, requery);
super(context, Song.TYPE_SONG, expandable, requery);
}
@Override

View File

@ -90,6 +90,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mTabHost.addTab(mTabHost.newTabSpec("tab_artists").setIndicator(res.getText(R.string.artists), res.getDrawable(R.drawable.tab_artists)).setContent(R.id.artist_list));
mTabHost.addTab(mTabHost.newTabSpec("tab_albums").setIndicator(res.getText(R.string.albums), res.getDrawable(R.drawable.tab_albums)).setContent(R.id.album_list));
mTabHost.addTab(mTabHost.newTabSpec("tab_songs").setIndicator(res.getText(R.string.songs), res.getDrawable(R.drawable.tab_songs)).setContent(R.id.song_list));
mTabHost.addTab(mTabHost.newTabSpec("tab_playlists").setIndicator(res.getText(R.string.playlists), res.getDrawable(R.drawable.tab_playlists)).setContent(R.id.playlist_list));
mSearchBox = findViewById(R.id.search_box);
@ -189,11 +190,10 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
long id = view.getMediaId();
int field = view.getFieldCount();
Intent intent = new Intent(this, PlaybackService.class);
intent.setAction(action);
intent.putExtra("type", field);
intent.putExtra("type", view.getMediaType());
intent.putExtra("id", id);
startService(intent);
@ -413,9 +413,10 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
{
switch (message.what) {
case MSG_INIT:
setupView(R.id.artist_list, new MediaAdapter(this, MediaAdapter.TYPE_ARTIST, true, false));
setupView(R.id.album_list, new MediaAdapter(this, MediaAdapter.TYPE_ALBUM, true, false));
setupView(R.id.artist_list, new MediaAdapter(this, Song.TYPE_ARTIST, true, false));
setupView(R.id.album_list, new MediaAdapter(this, Song.TYPE_ALBUM, true, false));
setupView(R.id.song_list, new SongMediaAdapter(this, false, false));
setupView(R.id.playlist_list, new MediaAdapter(this, Song.TYPE_PLAYLIST, false, true));
ContentResolver resolver = getContentResolver();
Observer observer = new Observer(mHandler);
@ -440,7 +441,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
runOnUiThread(new Runnable() {
public void run()
{
for (int i = 0; i != 3; ++i)
for (int i = 0; i != 4; ++i)
getAdapter(i).requery();
}
});

View File

@ -352,8 +352,10 @@ public final class SongTimeline {
* will be ordered by album and then by track number.
*
* @param enqueue If true, enqueue the set. If false, play the set.
* @param type 1, 2, or 3, indicating artist, album, or song, respectively.
* @param id The MediaStore id of the artist, album, or song.
* @param type The type represented by the id. Must be one of the
* Song.FIELD_* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
*/
public void chooseSongs(boolean enqueue, int type, long id)
{