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); }