Add a play all row

This commit is contained in:
Christopher Eby 2011-09-21 16:32:34 -05:00
parent 690789273b
commit f524be7199
8 changed files with 413 additions and 245 deletions

View File

@ -93,6 +93,9 @@ THE SOFTWARE.
<string name="none">None</string>
<string name="unknown">Unknown</string>
<string name="title_by_artist">%1$s by %2$s</string>
<string name="play_all">Play All</string>
<string name="enqueue_all">Enqueue All</string>
<string name="all_songs">All Songs</string>
<!-- Preferences -->
<plurals name="seconds">

View File

@ -22,7 +22,6 @@
package org.kreed.vanilla;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.database.DatabaseUtils;
@ -90,6 +89,15 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
* The section indexer, for the letter pop-up when scrolling.
*/
private MusicAlphabetIndexer mIndexer;
/**
* True if this adapter should have a special MediaView with custom text in
* the first row.
*/
private boolean mHasHeader;
/**
* The text to show in the header.
*/
private String mHeaderText;
/**
* Construct a MediaAdapter representing the given <code>type</code> of
@ -101,15 +109,17 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
* and what fields to display in the views.
* @param expandable Whether an expand arrow should be shown to the right
* of the views' text
* @param hasHeader Wether this view has a header row.
* @param limiter An initial limiter to use
*/
public MediaAdapter(SongSelector activity, int type, boolean expandable, Limiter limiter)
public MediaAdapter(SongSelector activity, int type, boolean expandable, boolean hasHeader, Limiter limiter)
{
super(activity, null, false);
mActivity = activity;
mType = type;
mExpandable = expandable;
mHasHeader = hasHeader;
mLimiter = limiter;
mIndexer = new MusicAlphabetIndexer(1);
@ -145,6 +155,75 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
}
}
@Override
public int getCount()
{
int count = super.getCount();
if (count == 0)
return 0;
else if (mHasHeader)
return count + 1;
else
return count;
}
@Override
public Object getItem(int pos)
{
if (mHasHeader) {
if (pos == 0)
return null;
else
pos -= 1;
}
return super.getItem(pos);
}
@Override
public long getItemId(int pos)
{
if (mHasHeader) {
if (pos == 0)
return -1;
else
pos -= 1;
}
return super.getItemId(pos);
}
@Override
public View getView(int pos, View convertView, ViewGroup parent)
{
if (mHasHeader) {
if (pos == 0) {
MediaView view;
if (convertView == null)
view = new MediaView(mActivity, mExpandable);
else
view = (MediaView)convertView;
view.makeHeader(mHeaderText);
return view;
} else {
pos -= 1;
}
}
return super.getView(pos, convertView, parent);
}
/**
* Modify the header text to be shown in the first row.
*
* @param text The new text.
*/
public void setHeaderText(String text)
{
mHeaderText = text;
notifyDataSetChanged();
}
/**
* Perform filtering on a background thread.
*
@ -159,14 +238,13 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
}
/**
* Query the backing content provider. Should be called on a background
* thread.
* Build the query to be run with runQuery().
*
* @param forceMusicCheck Force the is_music check to be added to the
* selection.
*/
public void runQuery()
public QueryTask buildQuery(boolean forceMusicCheck)
{
ContentResolver resolver = mActivity.getContentResolver();
Cursor cursor;
String constraint = mConstraint;
Limiter limiter = mLimiter;
@ -187,7 +265,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
else
sort = mFieldKeys[mFieldKeys.length - 1];
if (mType == MediaUtils.TYPE_SONG)
if (mType == MediaUtils.TYPE_SONG || forceMusicCheck)
selection.append("is_music!=0");
if (constraint != null && constraint.length() != 0) {
@ -228,8 +306,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
if (limiter != null && limiter.type == MediaUtils.TYPE_GENRE) {
// Genre is not standard metadata for MediaStore.Audio.Media.
// We have to query it through a separate provider. : /
QueryTask query = MediaUtils.buildGenreQuery(mLimiter.id, projection, selection.toString(), selectionArgs);
cursor = query.runQuery(resolver);
return MediaUtils.buildGenreQuery(mLimiter.id, projection, selection.toString(), selectionArgs);
} else {
if (limiter != null) {
if (selection.length() != 0)
@ -237,10 +314,8 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
selection.append(mLimiter.selection);
}
cursor = resolver.query(mStore, projection, selection.toString(), selectionArgs, sort);
return new QueryTask(mStore, projection, selection.toString(), selectionArgs, sort);
}
mActivity.changeCursor(this, cursor);
}
/**
@ -257,7 +332,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
* displayed media to only those that are children of a given parent
* media item.
*
* @param limiter The limiter, created by MediaView.getLimiter()
* @param limiter The limiter, created by {@link MediaAdapter#getLimiter(long)}.
*/
public final void setLimiter(Limiter limiter)
{
@ -277,31 +352,39 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
/**
* Builds a limiter based off of the media represented by the given row.
*
* @param view The row to create the limiter from.
* @param id The id of the row.
* @see MediaAdapter#getLimiter()
* @see MediaAdapter#setLimiter(MediaAdapter.Limiter)
*/
public Limiter getLimiter(MediaView view)
public Limiter getLimiter(long id)
{
long id = view.getMediaId();
String[] fields;
String selection = null;
Cursor cursor = getCursor();
if (cursor == null)
return null;
for (int i = 0, count = cursor.getCount(); i != count; ++i) {
cursor.moveToPosition(i);
if (cursor.getLong(0) == id)
break;
}
switch (mType) {
case MediaUtils.TYPE_ARTIST: {
fields = new String[] { view.getTitle() };
fields = new String[] { cursor.getString(1) };
String field = MediaStore.Audio.Media.ARTIST_ID;
selection = String.format("%s=%d", field, id);
break;
}
case MediaUtils.TYPE_ALBUM: {
fields = new String[] { view.getSubTitle(), view.getTitle() };
fields = new String[] { cursor.getString(2), cursor.getString(1) };
String field = MediaStore.Audio.Media.ALBUM_ID;
selection = String.format("%s=%d", field, id);
break;
}
case MediaUtils.TYPE_GENRE:
fields = new String[] { view.getTitle() };
fields = new String[] { cursor.getString(1) };
break;
default:
throw new IllegalStateException("getLimiter() is not supported for media type: " + mType);
@ -326,7 +409,13 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
@Override
public int getPositionForSection(int section)
{
return mIndexer.getPositionForSection(section);
int offset = 0;
if (mHasHeader) {
if (section == 0)
return 0;
offset = 1;
}
return offset + mIndexer.getPositionForSection(section);
}
@Override
@ -351,7 +440,7 @@ public class MediaAdapter extends CursorAdapter implements SectionIndexer {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent)
{
return new MediaView(context, this, mExpandable);
return new MediaView(context, mExpandable);
}
/**

View File

@ -187,41 +187,6 @@ public class MediaUtils {
}
}
/**
* Return an array containing all the song ids that match the specified parameters. Should be run on a background thread.
*
* @param context A context to use.
* @param type Type the id represents. Must be one of the Song.TYPE_*
* constants.
* @param id The id of the element in the MediaStore content provider for
* the given type.
*/
public static long[] getAllSongIdsWith(Context context, int type, long id)
{
if (type == TYPE_SONG)
return new long[] { id };
String[] projection = type == MediaUtils.TYPE_PLAYLIST ?
Song.EMPTY_PLAYLIST_PROJECTION : Song.EMPTY_PROJECTION;
Cursor cursor = buildQuery(type, id, projection, null).runQuery(context.getContentResolver());
if (cursor == null)
return null;
int count = cursor.getCount();
if (count == 0)
return null;
long[] songs = new long[count];
for (int i = 0; i != count; ++i) {
if (!cursor.moveToNext())
return null;
songs[i] = cursor.getLong(0);
}
cursor.close();
return songs;
}
/**
* Delete all the songs in the given media set. Should be run on a
* background thread.

View File

@ -76,10 +76,6 @@ public final class MediaView extends View {
sPaint.setAntiAlias(true);
}
/**
* The MediaAdapter that owns this view.
*/
private MediaAdapter mOwner;
/**
* The MediaStore id of the media represented by this view.
*/
@ -104,19 +100,21 @@ public final class MediaView extends View {
* The cached measured view height.
*/
private int mViewHeight;
/**
* True if this view is a header. Will override expandable setting to false.
*/
private boolean mIsHeader;
/**
* Construct a MediaView.
*
* @param context A Context to use.
* @param owner The MediaAdapter that owns this view.
* @param expandable True if the expander should be shown.
*/
public MediaView(Context context, MediaAdapter owner, boolean expandable)
public MediaView(Context context, boolean expandable)
{
super(context);
mOwner = owner;
mExpandable = expandable;
mViewHeight = (int)(7 * sTextSize / 2);
@ -148,7 +146,7 @@ public final class MediaView extends View {
Paint paint = sPaint;
if (mExpandable) {
if (mExpandable && !mIsHeader) {
Bitmap expander = sExpander;
width -= padding * 4 + expander.getWidth();
@ -188,24 +186,6 @@ public final class MediaView extends View {
paint.setShader(null);
}
/**
* Returns the MediaAdapter that owns this view.
*/
public MediaAdapter getOwner()
{
return mOwner;
}
/**
* Returns the type of media this view represents.
*
* @return One of MediaUtils.TYPE_*.
*/
public int getMediaType()
{
return mOwner.getMediaType();
}
/**
* Returns the MediaStore id of the media represented by this view.
*/
@ -222,14 +202,6 @@ public final class MediaView extends View {
return mTitle;
}
/**
* Returns the secondary text in the view, displayed on the lower line.
*/
public String getSubTitle()
{
return mSubTitle;
}
/**
* Returns true if the expander arrow was pressed in the last touch
* event.
@ -247,6 +219,18 @@ public final class MediaView extends View {
return mExpandable;
}
/**
* Set this view to be a header (custom text, never expandable).
*
* @param text The text to show.
*/
public void makeHeader(String text)
{
mIsHeader = true;
mTitle = text;
mSubTitle = null;
}
/**
* Update the fields in this view with the data from the given Cursor.
*
@ -258,6 +242,7 @@ public final class MediaView extends View {
*/
public void updateMedia(Cursor cursor, boolean useSecondary)
{
mIsHeader = false;
mId = cursor.getLong(0);
mTitle = cursor.getString(1);
if (useSecondary)
@ -265,13 +250,21 @@ public final class MediaView extends View {
invalidate();
}
/**
* Returns true if the view is set to be a "Play/Enqueue All" header.
*/
public boolean isHeader()
{
return mIsHeader;
}
/**
* Update mExpanderPressed.
*/
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (mExpandable)
if (mExpandable && !mIsHeader)
mExpanderPressed = event.getX() > getWidth() - sExpander.getWidth() - 2 * sTextSize;
return false;
}

View File

@ -24,6 +24,7 @@ package org.kreed.vanilla;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
@ -60,6 +61,10 @@ public class NewPlaylistDialog extends Dialog implements TextWatcher, View.OnCli
* action (e.g. "Create" or "Rename").
*/
private int mActionRes;
/**
* An intent that is simply stored in the dialog.
*/
private Intent mIntent;
/**
* Create a NewPlaylistDialog.
@ -69,12 +74,14 @@ public class NewPlaylistDialog extends Dialog implements TextWatcher, View.OnCli
* disabled when the EditText contains this text.
* @param actionText A string resource describing the default positive
* action (e.g. "Create").
* @param intent An optional intent to store with the dialog.
*/
public NewPlaylistDialog(Context context, String initialText, int actionText)
public NewPlaylistDialog(Context context, String initialText, int actionText, Intent intent)
{
super(context);
mInitialText = initialText;
mActionRes = actionText;
mIntent = intent;
}
@Override
@ -106,6 +113,14 @@ public class NewPlaylistDialog extends Dialog implements TextWatcher, View.OnCli
return mText.getText().toString();
}
/**
* Returns the stored intent.
*/
public Intent getIntent()
{
return mIntent;
}
public void afterTextChanged(Editable s)
{
// do nothing

View File

@ -142,17 +142,23 @@ public class Playlist {
}
/**
* Add the given set of song ids to the playlist with the given id.
* Run the given query and add the results to the given playlist. Should be
* run on a background thread.
*
* @param context A context to use.
* @param playlistId The MediaStore.Audio.Playlist id of the playlist to
* modify.
* @param toAdd The MediaStore ids of all the songs to be added to the
* playlist.
* @param query The query to run. The audio id should be the first column.
* @return The number of songs that were added to the playlist.
*/
public static void addToPlaylist(Context context, long playlistId, long[] toAdd)
public static int addToPlaylist(Context context, long playlistId, QueryTask query)
{
if (playlistId == -1)
return 0;
ContentResolver resolver = context.getContentResolver();
// Find the greatest PLAY_ORDER in the playlist
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId);
String[] projection = new String[] { MediaStore.Audio.Playlists.Members.PLAY_ORDER };
Cursor cursor = resolver.query(uri, projection, null, null, null);
@ -161,13 +167,25 @@ public class Playlist {
base = cursor.getInt(0) + 1;
cursor.close();
ContentValues[] values = new ContentValues[toAdd.length];
for (int i = 0; i != values.length; ++i) {
values[i] = new ContentValues(1);
values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i));
values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, toAdd[i]);
Cursor from = query.runQuery(resolver);
if (from == null)
return 0;
int count = from.getCount();
if (count > 0) {
ContentValues[] values = new ContentValues[count];
for (int i = 0; i != count; ++i) {
from.moveToPosition(i);
values[i] = new ContentValues(1);
values[i].put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, Integer.valueOf(base + i));
values[i].put(MediaStore.Audio.Playlists.Members.AUDIO_ID, from.getLong(0));
}
resolver.bulkInsert(uri, values);
}
resolver.bulkInsert(uri, values);
from.close();
return count;
}
/**

View File

@ -49,6 +49,26 @@ public class QueryTask {
mSortOrder = sortOrder;
}
/**
* Modify the uri of the pending query.
*
* @param uri The new uri.
*/
public void setUri(Uri uri)
{
mUri = uri;
}
/**
* Modify the projection of the pending query.
*
* @param projection The new projection.
*/
public void setProjection(String[] projection)
{
mProjection = projection;
}
/**
* Run the query. Should be called on a background thread.
*

View File

@ -151,11 +151,11 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mLimiterViews = (ViewGroup)findViewById(R.id.limiter_layout);
mArtistAdapter = setupView(R.id.artist_list, MediaUtils.TYPE_ARTIST, true, null);
mAlbumAdapter = setupView(R.id.album_list, MediaUtils.TYPE_ALBUM, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_albums"));
mSongAdapter = setupView(R.id.song_list, MediaUtils.TYPE_SONG, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_songs"));
mPlaylistAdapter = setupView(R.id.playlist_list, MediaUtils.TYPE_PLAYLIST, false, null);
mGenreAdapter = setupView(R.id.genre_list, MediaUtils.TYPE_GENRE, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_genres"));
mArtistAdapter = setupView(R.id.artist_list, MediaUtils.TYPE_ARTIST, true, true, null);
mAlbumAdapter = setupView(R.id.album_list, MediaUtils.TYPE_ALBUM, true, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_albums"));
mSongAdapter = setupView(R.id.song_list, MediaUtils.TYPE_SONG, false, true, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_songs"));
mPlaylistAdapter = setupView(R.id.playlist_list, MediaUtils.TYPE_PLAYLIST, false, false, null);
mGenreAdapter = setupView(R.id.genre_list, MediaUtils.TYPE_GENRE, true, false, state == null ? null : (MediaAdapter.Limiter)state.getSerializable("limiter_genres"));
// These should be in the same order as MediaUtils.TYPE_*
mAdapters = new MediaAdapter[] { mArtistAdapter, mAlbumAdapter, mSongAdapter, mPlaylistAdapter, mGenreAdapter };
@ -185,7 +185,8 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
mDefaultAction = Integer.parseInt(settings.getString("default_action_int", "0"));
mLastActedId = 0;
mLastActedId = -2;
updateHeaders();
}
@Override
@ -239,37 +240,58 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
}
/**
* Adds songs matching the type and id of the given view to the song timelime.
*
* @param view The MediaView to get type/id data from
* @param action One of SongSelector.ACTION_*
* Update the first row of the lists with the appropriate action (play all
* or enqueue all).
*/
private void pickSongs(MediaView view, int action)
private void updateHeaders()
{
int type = view.getMediaType();
long id = view.getMediaId();
int action = mDefaultAction;
if (action == ACTION_LAST_USED)
action = mLastAction;
else
mLastAction = action;
int mode = modeForAction[action];
String[] projection = type == MediaUtils.TYPE_PLAYLIST ?
Song.FILLED_PLAYLIST_PROJECTION : Song.FILLED_PROJECTION;
QueryTask query = MediaUtils.buildQuery(type, id, projection, null);
PlaybackService.get(this).addSongs(mode, query);
int res = action == ACTION_ENQUEUE ? R.string.enqueue_all : R.string.play_all;
String text = getString(res);
mArtistAdapter.setHeaderText(text);
mAlbumAdapter.setHeaderText(text);
mSongAdapter.setHeaderText(text);
}
/**
* "Expand" the view by setting the limiter from the given view and
* switching to the appropriate tab.
* Adds songs matching the data from the given intent to the song timelime.
*
* @param view The view to expand from.
* @param intent An intent created with
* {@link SongSelector#createClickIntent(MediaAdapter,MediaView)}.
* @param action One of SongSelector.ACTION_*
*/
private void expand(MediaView view)
private void pickSongs(Intent intent, int action)
{
mTabHost.setCurrentTab(setLimiter(view.getOwner().getLimiter(view)));
if (action == ACTION_LAST_USED)
action = mLastAction;
int mode = modeForAction[action];
QueryTask query = buildQueryFromIntent(intent, false);
PlaybackService.get(this).addSongs(mode, query);
mLastActedId = intent.getLongExtra("id", -1);
if (mDefaultAction == ACTION_LAST_USED && mLastAction != action) {
mLastAction = action;
updateHeaders();
}
}
/**
* "Expand" the view represented by the given intent by setting the limiter
* from the view and switching to the appropriate tab.
*
* @param intent An intent created with
* {@link SongSelector#createClickIntent(MediaAdapter,MediaView)}.
*/
private void expand(Intent intent)
{
int type = intent.getIntExtra("type", 1);
long id = intent.getLongExtra("id", -1);
mTabHost.setCurrentTab(setLimiter(mAdapters[type - 1].getLimiter(id)));
}
/**
@ -311,11 +333,11 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
{
MediaView mediaView = (MediaView)view;
if (mediaView.isExpanderPressed())
expand(mediaView);
expand(createClickIntent((MediaAdapter)list.getAdapter(), mediaView));
else if (id == mLastActedId)
startActivity(new Intent(this, FullPlaybackActivity.class));
else
pickSongs(mediaView, mDefaultAction);
pickSongs(createClickIntent((MediaAdapter)list.getAdapter(), mediaView), mDefaultAction);
}
public void afterTextChanged(Editable editable)
@ -407,8 +429,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
Cursor cursor = resolver.query(uri, projection, limiter.selection, null, null);
if (cursor != null) {
if (cursor.moveToNext()) {
limiter = new MediaAdapter.Limiter(cursor.getLong(0), MediaUtils.TYPE_ARTIST, MediaStore.Audio.Media.ARTIST_ID, new String[] { limiter.names[0] });
setLimiter(limiter);
setLimiter(mArtistAdapter.getLimiter(cursor.getLong(0)));
updateLimiterViews();
cursor.close();
return;
@ -424,6 +445,54 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
}
}
/**
* Creates an intent based off of the media represented by the given view.
*
* @param adapter The adapter that owns the view.
* @param view The MediaView to build from.
*/
private static Intent createClickIntent(MediaAdapter adapter, MediaView view)
{
Intent intent = new Intent();
intent.putExtra("type", adapter.getMediaType());
intent.putExtra("id", view.getMediaId());
intent.putExtra("isHeader", view.isHeader());
intent.putExtra("title", view.getTitle());
return intent;
}
/**
* Builds a media query based off the data stored in the given intent.
*
* @param intent An intent created with
* {@link SongSelector#createClickIntent(MediaAdapter,MediaView)}.
* @param empty If true, use the empty projection (only query id).
*/
private QueryTask buildQueryFromIntent(Intent intent, boolean empty)
{
int type = intent.getIntExtra("type", 1);
String[] projection;
if (type == MediaUtils.TYPE_PLAYLIST)
projection = empty ? Song.EMPTY_PLAYLIST_PROJECTION : Song.FILLED_PLAYLIST_PROJECTION;
else
projection = empty ? Song.EMPTY_PROJECTION : Song.FILLED_PROJECTION;
QueryTask query;
if (intent.getBooleanExtra("isHeader", false)) {
query = mAdapters[type - 1].buildQuery(true);
query.setProjection(projection);
// we want to query songs, not albums or artists
if (type != MediaUtils.TYPE_SONG)
query.setUri(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
} else {
long id = intent.getLongExtra("id", -1);
query = MediaUtils.buildQuery(type, id, projection, null);
}
return query;
}
private static final int MENU_PLAY = 0;
private static final int MENU_ENQUEUE = 1;
private static final int MENU_EXPAND = 2;
@ -432,70 +501,68 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
private static final int MENU_DELETE = 5;
private static final int MENU_EDIT = 6;
private static final int MENU_RENAME_PLAYLIST = 7;
private static final int MENU_SELECT_PLAYLIST = 8;
@Override
public void onCreateContextMenu(ContextMenu menu, View listView, ContextMenu.ContextMenuInfo absInfo)
{
MediaAdapter adapter = (MediaAdapter)((ListView)listView).getAdapter();
MediaView view = (MediaView)((AdapterView.AdapterContextMenuInfo)absInfo).targetView;
int type = view.getMediaType();
int id = (int)view.getMediaId();
menu.setHeaderTitle(view.getTitle());
menu.add(0, MENU_PLAY, 0, R.string.play);
menu.add(0, MENU_ENQUEUE, 0, R.string.enqueue);
if (view.getMediaType() == MediaUtils.TYPE_PLAYLIST) {
menu.add(0, MENU_RENAME_PLAYLIST, 0, R.string.rename);
menu.add(0, MENU_EDIT, 0, R.string.edit);
// Store view data in intent to avoid problems when the view data changes
// as worked is performed in the background.
Intent intent = createClickIntent(adapter, view);
if (view.isHeader())
menu.setHeaderTitle(getString(R.string.all_songs));
else
menu.setHeaderTitle(view.getTitle());
menu.add(0, MENU_PLAY, 0, R.string.play).setIntent(intent);
menu.add(0, MENU_ENQUEUE, 0, R.string.enqueue).setIntent(intent);
if (adapter == mPlaylistAdapter) {
menu.add(0, MENU_RENAME_PLAYLIST, 0, R.string.rename).setIntent(intent);
menu.add(0, MENU_EDIT, 0, R.string.edit).setIntent(intent);
}
SubMenu playlistMenu = menu.addSubMenu(0, MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
menu.addSubMenu(0, MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist).getItem().setIntent(intent);
if (view.hasExpanders())
menu.add(0, MENU_EXPAND, 0, R.string.expand);
menu.add(0, MENU_DELETE, 0, R.string.delete);
playlistMenu.add(type, MENU_NEW_PLAYLIST, id, R.string.new_playlist);
Playlist[] playlists = Playlist.getPlaylists(this);
if (playlists != null) {
for (int i = 0; i != playlists.length; ++i)
playlistMenu.add(type, (int)playlists[i].id + 100, id, playlists[i].name);
}
menu.add(0, MENU_EXPAND, 0, R.string.expand).setIntent(intent);
if (!view.isHeader())
menu.add(0, MENU_DELETE, 0, R.string.delete).setIntent(intent);
}
/**
* Add a set of songs to a playlists. Sets can be all songs from an artist,
* album, playlist, or a single song. Displays a Toast notifying of
* success.
* Add a set of songs represented by the intent to a playlist. Displays a
* Toast notifying of success.
*
* @param playlistId The MediaStore.Audio.Playlists id of the playlist to
* be modified.
* @param type The type of media the mediaId represents; one of the
* Song.TYPE_* constants.
* @param mediaId The MediaStore id of the element to be added.
* @param title The title of the playlist being added to (used for the
* Toast).
* @param playlistId The id of the playlist to add to.
* @param intent An intent created with
* {@link SongSelector#createClickIntent(MediaAdapter,MediaView)}.
*/
private void addToPlaylist(long playlistId, int type, long mediaId, CharSequence title)
private void addToPlaylist(long playlistId, Intent intent)
{
long[] ids = MediaUtils.getAllSongIdsWith(this, type, mediaId);
Playlist.addToPlaylist(this, playlistId, ids);
QueryTask query = buildQueryFromIntent(intent, true);
int count = Playlist.addToPlaylist(this, playlistId, query);
String message = getResources().getQuantityString(R.plurals.added_to_playlist, ids.length, ids.length, title);
String message = getResources().getQuantityString(R.plurals.added_to_playlist, count, count, intent.getStringExtra("playlistName"));
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
/**
* Delete the media with the specified type and id and show a Toast
* Delete the media represented by the given intent and show a Toast
* informing the user of this.
*
* @param type The type of media; one of the MediaUtils.TYPE_* constants.
* @param id The MediaStore id of the media.
* @param title The title of the playlist, to be displayed in the Toast.
* Only used when deleting a playlist.
* @param intent An intent created with
* {@link SongSelector#createClickIntent(MediaAdapter,MediaView)}.
*/
private void delete(int type, long id, String title)
private void delete(Intent intent)
{
int type = intent.getIntExtra("type", 1);
long id = intent.getLongExtra("id", -1);
if (type == MediaUtils.TYPE_PLAYLIST) {
Playlist.deletePlaylist(this, id);
String message = getResources().getString(R.string.playlist_deleted, title);
String message = getResources().getString(R.string.playlist_deleted, intent.getStringExtra("title"));
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
} else {
int count = MediaUtils.deleteMedia(this, type, id);
@ -507,58 +574,57 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
@Override
public boolean onContextItemSelected(MenuItem item)
{
int id = item.getItemId();
int type = item.getGroupId();
int mediaId = item.getOrder();
Intent intent = item.getIntent();
switch (id) {
switch (item.getItemId()) {
case MENU_EXPAND:
expand((MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView);
expand(intent);
break;
case MENU_ENQUEUE:
pickSongs((MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView, ACTION_ENQUEUE);
pickSongs(intent, ACTION_ENQUEUE);
break;
case MENU_PLAY:
pickSongs((MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView, ACTION_PLAY);
pickSongs(intent, ACTION_PLAY);
break;
case MENU_NEW_PLAYLIST: {
NewPlaylistDialog dialog = new NewPlaylistDialog(this, null, R.string.create);
Message message = mHandler.obtainMessage(MSG_NEW_PLAYLIST, type, mediaId);
message.obj = dialog;
dialog.setDismissMessage(message);
NewPlaylistDialog dialog = new NewPlaylistDialog(this, null, R.string.create, intent);
dialog.setDismissMessage(mHandler.obtainMessage(MSG_NEW_PLAYLIST, dialog));
dialog.show();
break;
}
case MENU_RENAME_PLAYLIST: {
MediaView view = (MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView;
NewPlaylistDialog dialog = new NewPlaylistDialog(this, view.getTitle(), R.string.rename);
Message message = mHandler.obtainMessage(MSG_RENAME_PLAYLIST, view.getMediaType(), (int)view.getMediaId());
message.obj = dialog;
dialog.setDismissMessage(message);
NewPlaylistDialog dialog = new NewPlaylistDialog(this, intent.getStringExtra("title"), R.string.rename, intent);
dialog.setDismissMessage(mHandler.obtainMessage(MSG_RENAME_PLAYLIST, dialog));
dialog.show();
break;
}
case MENU_DELETE: {
MediaView view = (MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView;
type = view.getMediaType();
if (type != MediaUtils.TYPE_PLAYLIST)
Toast.makeText(this, R.string.deleting, Toast.LENGTH_SHORT).show();
Message message = mHandler.obtainMessage(MSG_DELETE, type, (int)view.getMediaId());
message.obj = view.getTitle();
mHandler.sendMessage(message);
case MENU_DELETE:
mHandler.sendMessage(mHandler.obtainMessage(MSG_DELETE, intent));
break;
case MENU_ADD_TO_PLAYLIST: {
SubMenu playlistMenu = item.getSubMenu();
playlistMenu.add(0, MENU_NEW_PLAYLIST, 0, R.string.new_playlist).setIntent(intent);
Playlist[] playlists = Playlist.getPlaylists(this);
if (playlists != null) {
for (int i = 0; i != playlists.length; ++i) {
Intent copy = new Intent(intent);
copy.putExtra("playlist", playlists[i].id);
copy.putExtra("playlistName", playlists[i].name);
playlistMenu.add(0, MENU_SELECT_PLAYLIST, 0, playlists[i].name).setIntent(copy);
}
}
break;
}
case MENU_EDIT:
MediaView view = (MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView;
Intent intent = new Intent(Intent.ACTION_EDIT);
intent.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
intent.putExtra("playlist", String.valueOf(view.getMediaId()));
startActivity(intent);
case MENU_EDIT: {
Intent launch = new Intent(Intent.ACTION_EDIT);
launch.setDataAndType(Uri.EMPTY, "vnd.android.cursor.dir/track");
launch.putExtra("playlist", String.valueOf(intent.getLongExtra("id", 0)));
startActivity(launch);
break;
}
case MENU_SELECT_PLAYLIST:
mHandler.sendMessage(mHandler.obtainMessage(MSG_ADD_TO_PLAYLIST, intent));
break;
default:
if (id > 100)
addToPlaylist(id - 100, type, mediaId, item.getTitle());
return false;
}
return true;
@ -593,9 +659,10 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
* @param id The id of the ListView
* @param type The media type for the adapter.
* @param expandable True if the rows are expandable.
* @param hasHeader True if the view should have a header row.
* @param limiter The initial limiter to set on the adapter.
*/
private MediaAdapter setupView(int id, int type, boolean expandable, MediaAdapter.Limiter limiter)
private MediaAdapter setupView(int id, int type, boolean expandable, boolean hasHeader, MediaAdapter.Limiter limiter)
{
ListView view = (ListView)findViewById(id);
view.setOnItemClickListener(this);
@ -604,32 +671,24 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
view.setDivider(null);
view.setFastScrollEnabled(true);
MediaAdapter adapter = new MediaAdapter(this, type, expandable, limiter);
MediaAdapter adapter = new MediaAdapter(this, type, expandable, hasHeader, limiter);
view.setAdapter(adapter);
return adapter;
}
/**
* Call addToPlaylist with the parameters from the given message. The
* message must contain the type and id of the media to be added in
* arg1 and arg2, respectively. The obj field must be a NewPlaylistDialog
* that the name will be taken from.
* Call addToPlaylist with the results from a NewPlaylistDialog stored in
* obj.
*/
private static final int MSG_NEW_PLAYLIST = 11;
/**
* Delete the songs in the set of media with the specified type and id,
* given as arg1 and arg2, respectively. If type is a playlist, the
* playlist itself will be deleted, not the songs it contains. The obj
* field should contain the playlist name (as a String) if type is a
* playlist.
* Delete the songs represented by the intent stored in obj.
*/
private static final int MSG_DELETE = 12;
/**
* Rename the playlist with the parameters from the given message. The
* message must contain the type and id of the media to be added in
* arg1 and arg2, respectively. The obj field must be a NewPlaylistDialog
* that the name will be taken from.
* Call renamePlaylist with the results from a NewPlaylistDialog stored in
* obj.
*/
private static final int MSG_RENAME_PLAYLIST = 13;
/**
@ -637,32 +696,55 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
* obj will contain the MediaAdapter.
*/
public static final int MSG_RUN_QUERY = 14;
/**
* Call addToPlaylist with data from the intent in obj.
*/
public static final int MSG_ADD_TO_PLAYLIST = 15;
@Override
public boolean handleMessage(Message message)
{
switch (message.what) {
case MSG_ADD_TO_PLAYLIST: {
Intent intent = (Intent)message.obj;
addToPlaylist(intent.getLongExtra("playlist", -1), intent);
break;
}
case MSG_NEW_PLAYLIST: {
NewPlaylistDialog dialog = (NewPlaylistDialog)message.obj;
if (dialog.isAccepted()) {
String name = dialog.getText();
long playlistId = Playlist.createPlaylist(this, name);
addToPlaylist(playlistId, message.arg1, message.arg2, name);
Intent intent = dialog.getIntent();
intent.putExtra("playlistName", name);
addToPlaylist(playlistId, intent);
}
break;
}
case MSG_DELETE:
delete(message.arg1, message.arg2, (String)message.obj);
delete((Intent)message.obj);
break;
case MSG_RENAME_PLAYLIST: {
NewPlaylistDialog dialog = (NewPlaylistDialog)message.obj;
if (dialog.isAccepted())
Playlist.renamePlaylist(this, message.arg2, dialog.getText());
if (dialog.isAccepted()) {
long playlistId = dialog.getIntent().getLongExtra("id", -1);
Playlist.renamePlaylist(this, playlistId, dialog.getText());
}
break;
}
case MSG_RUN_QUERY:
((MediaAdapter)message.obj).runQuery();
case MSG_RUN_QUERY: {
final MediaAdapter adapter = (MediaAdapter)message.obj;
QueryTask query = adapter.buildQuery(false);
final Cursor cursor = query.runQuery(getContentResolver());
runOnUiThread(new Runnable() {
@Override
public void run()
{
adapter.changeCursor(cursor);
}
});
break;
}
default:
return super.handleMessage(message);
}
@ -681,23 +763,6 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
mHandler.sendMessage(mHandler.obtainMessage(MSG_RUN_QUERY, adapter));
}
/**
* Update the given adapter with the given cursor on the UI thread.
*
* @param adapter The adapter to update.
* @param cursor The cursor to update with.
*/
public void changeCursor(final MediaAdapter adapter, final Cursor cursor)
{
runOnUiThread(new Runnable() {
@Override
public void run()
{
adapter.changeCursor(cursor);
}
});
}
@Override
public void onMediaChange()
{