Add swipe actions to enqueue songs

This commit is contained in:
Christopher Eby 2011-09-11 14:27:19 -05:00
parent 027489b4a1
commit 8172618a09
8 changed files with 145 additions and 18 deletions

View File

@ -50,6 +50,9 @@ THE SOFTWARE.
<item>Previous Song</item>
<item>Toggle Repeat</item>
<item>Toggle Shuffle</item>
<item>Enqueue Current Album</item>
<item>Enqueue Current Artist</item>
<item>Enqueue Current Genre</item>
</string-array>
<string-array name="entry_values">
<!-- Note: even if this was an integer-array, these would be converted
@ -62,5 +65,8 @@ THE SOFTWARE.
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
</string-array>
</resources>

View File

@ -38,6 +38,10 @@ THE SOFTWARE.
<string name="repeat_enabling">Repeat enabled. The current song and any songs you have added after it will be repeated.</string>
<string name="repeat_disabling">Repeat disabled</string>
<string name="song_load_failed">Failed to load song %s. It may be corrupt or missing.</string>
<plurals name="enqueued_count">
<item quantity="one">1 song enqueued.</item>
<item quantity="other">%d songs enqueued.</item>
</plurals>
<!-- Widgets -->
<string name="starting">Starting up...</string>

View File

@ -59,30 +59,39 @@ public class MediaUtils {
* @param type One of the TYPE_* constants, excluding playlists.
* @param id The MediaStore id of the artist or album.
* @param projection The columns to query.
* @param select An extra selection to pass to the query, or null.
*/
private static Cursor getMediaCursor(int type, long id, String[] projection)
private static Cursor getMediaCursor(int type, long id, String[] projection, String select)
{
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
String selection;
StringBuilder selection = new StringBuilder();
switch (type) {
case TYPE_SONG:
selection = MediaStore.Audio.Media._ID;
selection.append(MediaStore.Audio.Media._ID);
break;
case TYPE_ARTIST:
selection = MediaStore.Audio.Media.ARTIST_ID;
selection.append(MediaStore.Audio.Media.ARTIST_ID);
break;
case TYPE_ALBUM:
selection = MediaStore.Audio.Media.ALBUM_ID;
selection.append(MediaStore.Audio.Media.ALBUM_ID);
break;
default:
throw new IllegalArgumentException("Invalid type specified: " + type);
}
selection += "=" + id + " AND " + MediaStore.Audio.Media.IS_MUSIC + "!=0";
selection.append('=');
selection.append(id);
selection.append(" AND is_music!=0");
if (select != null) {
selection.append(" AND ");
selection.append(select);
}
String sort = MediaStore.Audio.Media.ARTIST_KEY + ',' + MediaStore.Audio.Media.ALBUM_KEY + ',' + MediaStore.Audio.Media.TRACK;
return resolver.query(media, projection, selection, null, sort);
return resolver.query(media, projection, selection.toString(), null, sort);
}
/**
@ -124,24 +133,28 @@ public class MediaUtils {
* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
* @param selection An extra selection to be passed to the query. May be
* null. Must not be used with type == TYPE_SONG or type == TYPE_PLAYLIST
*/
public static long[] getAllSongIdsWith(int type, long id)
public static long[] getAllSongIdsWith(int type, long id, String selection)
{
Cursor cursor;
switch (type) {
case TYPE_SONG:
assert(selection == null);
return new long[] { id };
case TYPE_ARTIST:
case TYPE_ALBUM:
cursor = getMediaCursor(type, id, new String[] { MediaStore.Audio.Media._ID });
cursor = getMediaCursor(type, id, new String[] { MediaStore.Audio.Media._ID }, selection);
break;
case TYPE_PLAYLIST:
assert(selection == null);
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);
cursor = queryGenre(id, new String[] { MediaStore.Audio.Genres.Members._ID }, selection, null);
break;
default:
throw new IllegalArgumentException("Specified type not valid: " + type);
@ -178,7 +191,7 @@ public class MediaUtils {
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
String[] projection = new String [] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA };
Cursor cursor = getMediaCursor(type, id, projection);
Cursor cursor = getMediaCursor(type, id, projection, null);
if (cursor != null) {
PlaybackService service = ContextApplication.hasService() ? ContextApplication.getService() : null;
@ -200,6 +213,42 @@ public class MediaUtils {
return count;
}
/**
* Query the MediaStore to determine the id of the genre the song belongs
* to.
*/
public static long queryGenreForSong(long id)
{
// This is terribly inefficient, but it seems to be the only way to do
// this. Honeycomb introduced an API to query the genre of the song.
// We should look into it when ICS is released.
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
// query ids of all the genres
Uri uri = MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI;
String[] projection = { "_id" };
Cursor cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null) {
String selection = "_id=" + id;
while (cursor.moveToNext()) {
// check if the given song belongs to this genre
long genreId = cursor.getLong(0);
Uri genreUri = MediaStore.Audio.Genres.Members.getContentUri("external", genreId);
Cursor c = resolver.query(genreUri, projection, selection, null, null);
if (c != null) {
if (c.getCount() == 1)
return genreId;
c.close();
}
}
cursor.close();
}
return -1;
}
/**
* Shuffle an array using Fisher-Yates algorithm.
*

View File

@ -36,6 +36,7 @@ import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
public class PlaybackActivity extends Activity implements Handler.Callback, View.OnClickListener, CoverView.Callback {
public static final int ACTION_NOTHING = 0;
@ -45,6 +46,9 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
public static final int ACTION_PREVIOUS_SONG = 4;
public static final int ACTION_REPEAT = 5;
public static final int ACTION_SHUFFLE = 6;
public static final int ACTION_ENQUEUE_ALBUM = 7;
public static final int ACTION_ENQUEUE_ARTIST = 8;
public static final int ACTION_ENQUEUE_GENRE = 9;
public static int mUpAction;
public static int mDownAction;
@ -359,6 +363,13 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
startActivity(new Intent(this, SongSelector.class));
}
public void enqueue(int type)
{
int count = ContextApplication.getService().enqueueFromCurrent(type);
String text = getResources().getQuantityString(R.plurals.enqueued_count, count, count);
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
}
public void performAction(int action)
{
switch (action) {
@ -382,6 +393,15 @@ public class PlaybackActivity extends Activity implements Handler.Callback, View
case ACTION_SHUFFLE:
toggleShuffle();
break;
case ACTION_ENQUEUE_ALBUM:
enqueue(MediaUtils.TYPE_ALBUM);
break;
case ACTION_ENQUEUE_ARTIST:
enqueue(MediaUtils.TYPE_ARTIST);
break;
case ACTION_ENQUEUE_GENRE:
enqueue(MediaUtils.TYPE_GENRE);
break;
default:
throw new IllegalArgumentException("Invalid action: " + action);
}

View File

@ -981,7 +981,7 @@ public final class PlaybackService extends Service implements Handler.Callback,
*/
public Song playSongs(int type, long id)
{
mTimeline.chooseSongs(false, type, id);
mTimeline.chooseSongs(false, type, id, null);
return setCurrentSong(+1);
}
@ -999,11 +999,48 @@ public final class PlaybackService extends Service implements Handler.Callback,
*/
public void enqueueSongs(int type, long id)
{
mTimeline.chooseSongs(true, type, id);
mTimeline.chooseSongs(true, type, id, null);
mHandler.removeMessages(SAVE_STATE);
mHandler.sendEmptyMessageDelayed(SAVE_STATE, 5000);
}
/**
* Enqueues all the songs with the same album/artist/genre as the current
* song.
*
* This will clear the queue and place the first song from the group after
* the playing song.
*
* @param type The media type, one of MediaUtils.TYPE_ALBUM, TYPE_ARTIST,
* or TYPE_GENRE
* @return The number of songs that were enqueued.
*/
public int enqueueFromCurrent(int type)
{
Song current = getSong(0);
if (current == null)
return 0;
long id = -1;
switch (type) {
case MediaUtils.TYPE_ARTIST:
id = current.artistId;
break;
case MediaUtils.TYPE_ALBUM:
id = current.albumId;
break;
case MediaUtils.TYPE_GENRE:
id = MediaUtils.queryGenreForSong(current.id);
break;
default:
throw new IllegalArgumentException("Unsupported media type: " + type);
}
String selection = "_id!=" + current.id;
int count = mTimeline.chooseSongs(false, type, id, selection);
return count;
}
/**
* Reset the position at which songs are enqueued. That is, the next song
* enqueued will be placed directly after the playing song.

View File

@ -86,6 +86,7 @@ public class Song implements Parcelable {
MediaStore.Audio.Media.ALBUM,
MediaStore.Audio.Media.ARTIST,
MediaStore.Audio.Media.ALBUM_ID,
MediaStore.Audio.Media.ARTIST_ID,
MediaStore.Audio.Media.DURATION
};
@ -97,6 +98,10 @@ public class Song implements Parcelable {
* Id of this song's album in the MediaStore
*/
public long albumId;
/**
* Id of this song's artist in the MediaStore
*/
public long artistId;
/**
* Path to the data for this song
@ -293,7 +298,8 @@ public class Song implements Parcelable {
album = cursor.getString(3);
artist = cursor.getString(4);
albumId = cursor.getLong(5);
duration = cursor.getLong(6);
artistId = cursor.getLong(6);
duration = cursor.getLong(7);
}
/**

View File

@ -466,7 +466,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
*/
private void addToPlaylist(long playlistId, int type, long mediaId, CharSequence title)
{
long[] ids = MediaUtils.getAllSongIdsWith(type, mediaId);
long[] ids = MediaUtils.getAllSongIdsWith(type, mediaId, null);
Playlist.addToPlaylist(playlistId, ids);
String message = getResources().getQuantityString(R.plurals.added_to_playlist, ids.length, ids.length, title);

View File

@ -367,12 +367,15 @@ public final class SongTimeline {
* MediaUtils.FIELD_* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
* @param selection An extra selection to be passed to the query. May be
* null. Must not be used with type == TYPE_SONG or type == TYPE_PLAYLIST
* @return The number of songs that were enqueued.
*/
public void chooseSongs(boolean enqueue, int type, long id)
public int chooseSongs(boolean enqueue, int type, long id, String selection)
{
long[] songs = MediaUtils.getAllSongIdsWith(type, id);
long[] songs = MediaUtils.getAllSongIdsWith(type, id, selection);
if (songs == null || songs.length == 0)
return;
return 0;
if (mShuffle)
MediaUtils.shuffle(songs);
@ -404,6 +407,8 @@ public final class SongTimeline {
Song newSong = getSong(+1);
if (newSong != oldSong && mCallback != null)
mCallback.songReplaced(+1, newSong);
return songs.length;
}
/**