Add swipe actions to enqueue songs
This commit is contained in:
parent
027489b4a1
commit
8172618a09
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user