diff --git a/res/layout/song_selector.xml b/res/layout/song_selector.xml
index 0eb74890..edd74192 100644
--- a/res/layout/song_selector.xml
+++ b/res/layout/song_selector.xml
@@ -67,6 +67,12 @@ THE SOFTWARE.
android:layout_height="fill_parent"
android:divider="@null"
android:fastScrollEnabled="true" />
+
@@ -103,4 +109,4 @@ THE SOFTWARE.
android:layout_alignParentRight="true"
android:src="@android:drawable/ic_menu_close_clear_cancel" />
-
\ No newline at end of file
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e1e64226..784cd543 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -79,6 +79,7 @@ THE SOFTWARE.
Albums
Songs
Playlists
+ Genres
None
Unknown
diff --git a/src/org/kreed/vanilla/MediaAdapter.java b/src/org/kreed/vanilla/MediaAdapter.java
index 394a57fa..e4898575 100644
--- a/src/org/kreed/vanilla/MediaAdapter.java
+++ b/src/org/kreed/vanilla/MediaAdapter.java
@@ -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);
}
}
}
diff --git a/src/org/kreed/vanilla/MediaUtils.java b/src/org/kreed/vanilla/MediaUtils.java
index 7c5afd50..0b219f89 100644
--- a/src/org/kreed/vanilla/MediaUtils.java
+++ b/src/org/kreed/vanilla/MediaUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Christopher Eby
+ * Copyright (C) 2010, 2011 Christopher Eby
*
* 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);
}
diff --git a/src/org/kreed/vanilla/SongSelector.java b/src/org/kreed/vanilla/SongSelector.java
index 17cf4e87..755e1909 100644
--- a/src/org/kreed/vanilla/SongSelector.java
+++ b/src/org/kreed/vanilla/SongSelector.java
@@ -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);
}