Add genre tab to library

This commit is contained in:
Christopher Eby 2011-08-28 01:50:47 -05:00
parent d1d875fd5f
commit 33374c2ebd
5 changed files with 80 additions and 25 deletions

View File

@ -67,6 +67,12 @@ THE SOFTWARE.
android:layout_height="fill_parent"
android:divider="@null"
android:fastScrollEnabled="true" />
<ListView
android:id="@+id/genre_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@null"
android:fastScrollEnabled="true" />
</FrameLayout>
</LinearLayout>
</TabHost>
@ -103,4 +109,4 @@ THE SOFTWARE.
android:layout_alignParentRight="true"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
</RelativeLayout>
</merge>
</merge>

View File

@ -79,6 +79,7 @@ THE SOFTWARE.
<string name="albums">Albums</string>
<string name="songs">Songs</string>
<string name="playlists">Playlists</string>
<string name="genres">Genres</string>
<string name="none">None</string>
<string name="unknown">Unknown</string>

View File

@ -139,6 +139,11 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
mFields = new String[] { MediaStore.Audio.Playlists.NAME };
mFieldKeys = null;
break;
case MediaUtils.TYPE_GENRE:
mStore = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI;
mFields = new String[] { MediaStore.Audio.Genres.NAME };
mFieldKeys = null;
break;
default:
throw new IllegalArgumentException("Invalid value for type: " + type);
}
@ -195,7 +200,7 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
* method may be overridden in subclasses to exclude certain media from the
* adapter.
*
* @return The selection, formatted as an SQL WHERE clause or null for to
* @return The selection, formatted as an SQL WHERE clause or null to
* accept all media.
*/
protected String getDefaultSelection()
@ -222,19 +227,19 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
{
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
String[] projection;
if (mFields.length == 1)
projection = new String[] { BaseColumns._ID, mFields[0] };
else
projection = new String[] { BaseColumns._ID, mFields[mFields.length - 1], mFields[0] };
StringBuilder selection = new StringBuilder();
String[] selectionArgs;
String[] selectionArgs = null;
String defaultSelection = getDefaultSelection();
if (defaultSelection != null)
selection.append(defaultSelection);
if (mLimiter != null) {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(mLimiter.selection);
}
if (constraint != null && constraint.length() != 0) {
String[] needles;
@ -251,33 +256,36 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
int size = needles.length;
selectionArgs = new String[size];
int i = 0;
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 != needles.length; ++i, ++j) {
selectionArgs[i] = '%' + needles[j] + '%';
for (int j = 0; j != needles.length; ++j) {
selectionArgs[j] = '%' + needles[j] + '%';
// If we have something in the selection args (i.e. i > 0), we
// If we have something in the selection args (i.e. j > 0), we
// must have something in the selection, so we can skip the more
// costly direct check of the selection length.
if (i != 0 || selection.length() != 0)
if (j != 0 || selection.length() != 0)
selection.append(" AND ");
selection.append(keys);
selection.append(" LIKE ?");
}
} else {
selectionArgs = null;
}
String[] projection;
if (mFields.length == 1)
projection = new String[] { BaseColumns._ID, mFields[0] };
else
projection = new String[] { BaseColumns._ID, mFields[mFields.length - 1], mFields[0] };
if (mLimiter != null) {
if (mLimiter.type == MediaUtils.TYPE_GENRE) {
// Genre is not standard metadata for MediaStore.Audio.Media.
// We have to query it through a separate provider. : /
return MediaUtils.queryGenre(mLimiter.id, projection, selection.toString(), selectionArgs);
} else {
if (selection.length() != 0)
selection.append(" AND ");
selection.append(mLimiter.selection);
}
}
return resolver.query(mStore, projection, selection.toString(), selectionArgs, getSortOrder());
}
@ -553,6 +561,10 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
fields = new String[] { mSubTitle, mTitle };
field = MediaStore.Audio.Media.ALBUM_ID;
break;
case MediaUtils.TYPE_GENRE:
fields = new String[] { mTitle };
field = null;
break;
default:
throw new IllegalStateException("getLimiter() is not supported for media type: " + mType);
}
@ -576,6 +588,7 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
*/
public static class Limiter implements Serializable {
public final String[] names;
public final long id;
public final int type;
public final String selection;
@ -583,7 +596,8 @@ public class MediaAdapter extends CursorAdapter implements FilterQueryProvider {
{
this.type = type;
this.names = names;
selection = String.format("%s=%d", field, id);
this.id = id;
selection = field == null ? null : String.format("%s=%d", field, id);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Christopher Eby <kreed@kreed.org>
* Copyright (C) 2010, 2011 Christopher Eby <kreed@kreed.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -47,6 +47,10 @@ public class MediaUtils {
* Type indicating an id represents a playlist.
*/
public static final int TYPE_PLAYLIST = 4;
/**
* Type indicating ids represent genres.
*/
public static final int TYPE_GENRE = 5;
/**
* Return a cursor containing the ids of all the songs with artist or
@ -96,6 +100,23 @@ public class MediaUtils {
return resolver.query(uri, projection, null, null, sort);
}
/**
* Return a cursor containing the ids of all the songs in the genre
* with the given id.
*
* @param id The id of the genre in MediaStore.Audio.Genres.
* @param projection The columns to query.
* @param selection The selection to pass to the query, or null.
* @param selectionArgs The arguments to substitute into the selection.
*/
public static Cursor queryGenre(long id, String[] projection, String selection, String[] selectionArgs)
{
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
Uri uri = MediaStore.Audio.Genres.Members.getContentUri("external", id);
String sort = MediaStore.Audio.Genres.Members.TITLE_KEY;
return resolver.query(uri, projection, selection, selectionArgs, sort);
}
/**
* Return an array containing all the song ids that match the specified parameters
*
@ -118,6 +139,10 @@ public class MediaUtils {
case TYPE_PLAYLIST:
cursor = getPlaylistCursor(id, new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID });
break;
case TYPE_GENRE:
// NOTE: AUDIO_ID does not seem to work here, strangely.
cursor = queryGenre(id, new String[] { MediaStore.Audio.Genres.Members._ID }, null, null);
break;
default:
throw new IllegalArgumentException("Specified type not valid: " + type);
}

View File

@ -61,7 +61,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
/**
* The number of tabs in the song selector.
*/
private static final int TAB_COUNT = 4;
private static final int TAB_COUNT = 5;
private TabHost mTabHost;
@ -86,6 +86,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private MediaAdapter mAlbumAdapter;
private MediaAdapter mSongAdapter;
private MediaAdapter mPlaylistAdapter;
private MediaAdapter mGenreAdapter;
private MediaAdapter mCurrentAdapter;
@Override
@ -103,6 +104,8 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
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));
// TODO: find/create genre icon
mTabHost.addTab(mTabHost.newTabSpec("tab_genres").setIndicator(res.getText(R.string.genres), res.getDrawable(R.drawable.tab_songs)).setContent(R.id.genre_list));
mSearchBox = findViewById(R.id.search_box);
@ -118,7 +121,8 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mAlbumAdapter = setupView(R.id.album_list, new MediaAdapter(this, MediaUtils.TYPE_ALBUM, true, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_albums")));
mSongAdapter = setupView(R.id.song_list, new SongMediaAdapter(this, false, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_songs")));
mPlaylistAdapter = setupView(R.id.playlist_list, new MediaAdapter(this, MediaUtils.TYPE_PLAYLIST, false, true, null));
mAdapters = new MediaAdapter[] { mArtistAdapter, mAlbumAdapter, mSongAdapter, mPlaylistAdapter };
mGenreAdapter = setupView(R.id.genre_list, new MediaAdapter(this, MediaUtils.TYPE_GENRE, true, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_genres")));
mAdapters = new MediaAdapter[] { mArtistAdapter, mAlbumAdapter, mSongAdapter, mPlaylistAdapter, mGenreAdapter };
mTabHost.setOnTabChangedListener(this);
@ -185,6 +189,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
out.putString("filter", mTextFilter.getText().toString());
out.putSerializable("limiter_albums", mAlbumAdapter.getLimiter());
out.putSerializable("limiter_songs", mSongAdapter.getLimiter());
out.putSerializable("limiter_genres", mGenreAdapter.getLimiter());
}
@Override
@ -289,6 +294,10 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mAlbumAdapter.setLimiter(limiter, false);
mSongAdapter.setLimiter(limiter, true);
return 1;
case MediaUtils.TYPE_GENRE:
mAlbumAdapter.setLimiter(null, true);
mSongAdapter.setLimiter(limiter, false);
return 2;
default:
throw new IllegalArgumentException("Unsupported limiter type: " + limiter.type);
}