Allow songs and playlists to be deleted.
This commit is contained in:
parent
cc33825cfb
commit
5d4dd5e0a6
@ -70,5 +70,6 @@
|
||||
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="8" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<supports-screens android:smallScreens="true" />
|
||||
</manifest>
|
||||
|
@ -49,6 +49,7 @@
|
||||
<string name="add_to_playlist">Add to Playlist...</string>
|
||||
<string name="new_playlist">New Playlist...</string>
|
||||
<string name="expand">Expand</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="playback_view">Now Playing</string>
|
||||
<string name="search">Search</string>
|
||||
|
||||
@ -58,6 +59,12 @@
|
||||
<item quantity="one">1 song added to playlist %2$s.</item>
|
||||
<item quantity="other">%d songs added to playlist %s.</item>
|
||||
</plurals>
|
||||
<string name="deleting">Deleting...</string>
|
||||
<string name="playlist_deleted">Playlist %s deleted.</string>
|
||||
<plurals name="deleted">
|
||||
<item quantity="one">1 song deleted.</item>
|
||||
<item quantity="other">%d songs deleted.</item>
|
||||
</plurals>
|
||||
|
||||
<string name="artists">Artists</string>
|
||||
<string name="albums">Albums</string>
|
||||
|
@ -18,6 +18,8 @@
|
||||
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@ -45,28 +47,31 @@ public class MediaUtils {
|
||||
* Return a cursor containing the ids of all the songs with artist or
|
||||
* album of the specified id.
|
||||
*
|
||||
* @param type TYPE_ARTIST or TYPE_ALBUM, indicating the the id represents
|
||||
* an artist or album
|
||||
* @param id The MediaStore id of the artist or album
|
||||
* @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.
|
||||
*/
|
||||
private static Cursor getMediaCursor(int type, long id)
|
||||
private static Cursor getMediaCursor(int type, long id, String[] projection)
|
||||
{
|
||||
String selection = "=" + id + " AND " + MediaStore.Audio.Media.IS_MUSIC + "!=0";
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
String selection;
|
||||
|
||||
switch (type) {
|
||||
case TYPE_SONG:
|
||||
selection = MediaStore.Audio.Media._ID;
|
||||
break;
|
||||
case TYPE_ARTIST:
|
||||
selection = MediaStore.Audio.Media.ARTIST_ID + selection;
|
||||
selection = MediaStore.Audio.Media.ARTIST_ID;
|
||||
break;
|
||||
case TYPE_ALBUM:
|
||||
selection = MediaStore.Audio.Media.ALBUM_ID + selection;
|
||||
selection = MediaStore.Audio.Media.ALBUM_ID;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type specified: " + type);
|
||||
}
|
||||
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
Uri media = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
String[] projection = { MediaStore.Audio.Media._ID };
|
||||
selection += "=" + id + " AND " + MediaStore.Audio.Media.IS_MUSIC + "!=0";
|
||||
String sort = MediaStore.Audio.Media.ARTIST_KEY + ',' + MediaStore.Audio.Media.ALBUM_KEY + ',' + MediaStore.Audio.Media.TRACK;
|
||||
return resolver.query(media, projection, selection, null, sort);
|
||||
}
|
||||
@ -76,12 +81,12 @@ public class MediaUtils {
|
||||
* with the given id.
|
||||
*
|
||||
* @param id The id of the playlist in MediaStore.Audio.Playlists.
|
||||
* @param projection The columns to query.
|
||||
*/
|
||||
private static Cursor getPlaylistCursor(long id)
|
||||
private static Cursor getPlaylistCursor(long id, String[] projection)
|
||||
{
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external", id);
|
||||
String[] projection = new String[] { MediaStore.Audio.Playlists.Members.AUDIO_ID };
|
||||
String sort = MediaStore.Audio.Playlists.Members.PLAY_ORDER;
|
||||
return resolver.query(uri, projection, null, null, sort);
|
||||
}
|
||||
@ -96,6 +101,7 @@ public class MediaUtils {
|
||||
*/
|
||||
public static long[] getAllSongIdsWith(int type, long id)
|
||||
{
|
||||
String[] projection = { MediaStore.Audio.Media._ID };
|
||||
Cursor cursor;
|
||||
|
||||
switch (type) {
|
||||
@ -103,10 +109,10 @@ public class MediaUtils {
|
||||
return new long[] { id };
|
||||
case TYPE_ARTIST:
|
||||
case TYPE_ALBUM:
|
||||
cursor = getMediaCursor(type, id);
|
||||
cursor = getMediaCursor(type, id, projection);
|
||||
break;
|
||||
case TYPE_PLAYLIST:
|
||||
cursor = getPlaylistCursor(id);
|
||||
cursor = getPlaylistCursor(id, projection);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Specified type not valid: " + type);
|
||||
@ -129,4 +135,39 @@ public class MediaUtils {
|
||||
cursor.close();
|
||||
return songs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the songs in the given media set.
|
||||
*
|
||||
* @param type One of the TYPE_* constants, excluding playlists.
|
||||
* @param id The MediaStore id of the media to delete.
|
||||
* @return The number of songs deleted.
|
||||
*/
|
||||
public static int deleteMedia(int type, long id)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
ContentResolver resolver = ContextApplication.getContext().getContentResolver();
|
||||
String[] projection = new String [] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.DATA };
|
||||
Cursor cursor = getMediaCursor(type, id, projection);
|
||||
|
||||
if (cursor != null) {
|
||||
PlaybackService service = ContextApplication.hasService() ? ContextApplication.getService() : null;
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
if (new File(cursor.getString(1)).delete()) {
|
||||
long songId = cursor.getLong(0);
|
||||
String where = MediaStore.Audio.Media._ID + '=' + songId;
|
||||
resolver.delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, where, null);
|
||||
if (service != null)
|
||||
service.removeSong(songId);
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
@ -570,6 +570,8 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
/**
|
||||
* Returns the song <code>delta</code> places away from the current
|
||||
* position.
|
||||
*
|
||||
* @see SongTimeline#getSong(int)
|
||||
*/
|
||||
public Song getSong(int delta)
|
||||
{
|
||||
@ -835,6 +837,8 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
|
||||
/**
|
||||
* Returns the position of the current song in the song timeline.
|
||||
*
|
||||
* @see SongTimeline#getCurrentPosition()
|
||||
*/
|
||||
public int getTimelinePos()
|
||||
{
|
||||
@ -869,4 +873,18 @@ public final class PlaybackService extends Service implements Handler.Callback,
|
||||
{
|
||||
broadcastReplaceSong(delta, song);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the song with the given id from the timeline and advance to the
|
||||
* next song if the given song is currently playing.
|
||||
*
|
||||
* @param id The MediaStore id of the song to remove.
|
||||
* @see SongTimeline#removeSong(long)
|
||||
*/
|
||||
public void removeSong(long id)
|
||||
{
|
||||
boolean shouldAdvance = mTimeline.removeSong(id);
|
||||
if (shouldAdvance)
|
||||
setCurrentSong(0);
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
package org.kreed.vanilla;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
@ -159,4 +160,15 @@ public class Playlist {
|
||||
}
|
||||
resolver.bulkInsert(uri, values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the playlist with the given id.
|
||||
*
|
||||
* @param id The Media.Audio.Playlists id of the playlist.
|
||||
*/
|
||||
public static void deletePlaylist(long id)
|
||||
{
|
||||
Uri uri = ContentUris.withAppendedId(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, id);
|
||||
ContextApplication.getContext().getContentResolver().delete(uri, null, null);
|
||||
}
|
||||
}
|
||||
|
@ -179,6 +179,19 @@ public class Song implements Parcelable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the id of the given song.
|
||||
*
|
||||
* @param song The Song to get the id from.
|
||||
* @return The id, or 0 if the given song is null.
|
||||
*/
|
||||
public static long getId(Song song)
|
||||
{
|
||||
if (song == null)
|
||||
return 0;
|
||||
return song.id;
|
||||
}
|
||||
|
||||
public static Parcelable.Creator<Song> CREATOR = new Parcelable.Creator<Song>() {
|
||||
public Song createFromParcel(Parcel in)
|
||||
{
|
||||
|
@ -329,6 +329,7 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
private static final int MENU_EXPAND = 2;
|
||||
private static final int MENU_ADD_TO_PLAYLIST = 3;
|
||||
private static final int MENU_NEW_PLAYLIST = 4;
|
||||
private static final int MENU_DELETE = 5;
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View listView, ContextMenu.ContextMenuInfo absInfo)
|
||||
@ -343,11 +344,14 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
SubMenu playlistMenu = menu.addSubMenu(0, MENU_ADD_TO_PLAYLIST, 0, R.string.add_to_playlist);
|
||||
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();
|
||||
for (int i = 0; i != playlists.length; ++i)
|
||||
playlistMenu.add(type, (int)playlists[i].id + 100, id, playlists[i].name);
|
||||
if (playlists != null) {
|
||||
for (int i = 0; i != playlists.length; ++i)
|
||||
playlistMenu.add(type, (int)playlists[i].id + 100, id, playlists[i].name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -371,21 +375,41 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
String message = getResources().getQuantityString(R.plurals.added_to_playlist, ids.length, ids.length, title);
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete the media with the specified type and id 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.
|
||||
*/
|
||||
private void delete(int type, long id, String title)
|
||||
{
|
||||
if (type == MediaUtils.TYPE_PLAYLIST) {
|
||||
Playlist.deletePlaylist(id);
|
||||
String message = getResources().getString(R.string.playlist_deleted, title);
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
int count = MediaUtils.deleteMedia(type, id);
|
||||
String message = getResources().getQuantityString(R.plurals.deleted, count, count);
|
||||
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item)
|
||||
{
|
||||
String action = PlaybackService.ACTION_PLAY_ITEMS;
|
||||
int id = item.getItemId();
|
||||
final int type = item.getGroupId();
|
||||
final int mediaId = item.getOrder();
|
||||
int type = item.getGroupId();
|
||||
int mediaId = item.getOrder();
|
||||
|
||||
switch (id) {
|
||||
case MENU_EXPAND:
|
||||
expand((MediaAdapter.MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView);
|
||||
break;
|
||||
case MENU_ADD_TO_PLAYLIST:
|
||||
break;
|
||||
case MENU_ENQUEUE:
|
||||
action = PlaybackService.ACTION_ENQUEUE_ITEMS;
|
||||
// fall through
|
||||
@ -394,13 +418,24 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
mDefaultAction = action;
|
||||
sendSongIntent((MediaAdapter.MediaView)((AdapterView.AdapterContextMenuInfo)item.getMenuInfo()).targetView, action);
|
||||
break;
|
||||
case MENU_NEW_PLAYLIST:
|
||||
case MENU_NEW_PLAYLIST: {
|
||||
NewPlaylistDialog dialog = new NewPlaylistDialog(this);
|
||||
Message message = mHandler.obtainMessage(MSG_NEW_PLAYLIST, type, mediaId);
|
||||
message.obj = dialog;
|
||||
dialog.setDismissMessage(message);
|
||||
dialog.show();
|
||||
break;
|
||||
}
|
||||
case MENU_DELETE: {
|
||||
MediaAdapter.MediaView view = (MediaAdapter.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);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (id > 100)
|
||||
addToPlaylist(id - 100, type, mediaId, item.getTitle());
|
||||
@ -461,12 +496,20 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
*/
|
||||
private static final int MSG_INIT = 10;
|
||||
/**
|
||||
* Call addToPlaylist with the paramaters from the given message. The
|
||||
* 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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
private static final int MSG_DELETE = 12;
|
||||
|
||||
@Override
|
||||
public boolean handleMessage(Message message)
|
||||
@ -485,6 +528,9 @@ public class SongSelector extends PlaybackActivity implements AdapterView.OnItem
|
||||
addToPlaylist(playlistId, message.arg1, message.arg2, name);
|
||||
}
|
||||
break;
|
||||
case MSG_DELETE:
|
||||
delete(message.arg1, message.arg2, (String)message.obj);
|
||||
break;
|
||||
default:
|
||||
return super.handleMessage(message);
|
||||
}
|
||||
|
@ -428,4 +428,54 @@ public final class SongTimeline {
|
||||
mQueueOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the song with the given id from the timeline.
|
||||
*
|
||||
* @param id The MediaStore id of the song to remove.
|
||||
* @return True if the current song has changed.
|
||||
*/
|
||||
public boolean removeSong(long id)
|
||||
{
|
||||
synchronized (this) {
|
||||
boolean changed = false;
|
||||
ArrayList<Song> songs = mSongs;
|
||||
|
||||
int i = mCurrentPos;
|
||||
Song oldPrevious = getSong(-1);
|
||||
Song oldCurrent = getSong(0);
|
||||
Song oldNext = getSong(+1);
|
||||
|
||||
while (--i != -1) {
|
||||
if (Song.getId(songs.get(i)) == id) {
|
||||
songs.remove(i);
|
||||
--mCurrentPos;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = mCurrentPos; i != songs.size(); ++i) {
|
||||
if (Song.getId(songs.get(i)) == id)
|
||||
songs.remove(i);
|
||||
}
|
||||
|
||||
i = mCurrentPos;
|
||||
Song previous = getSong(-1);
|
||||
Song current = getSong(0);
|
||||
Song next = getSong(+1);
|
||||
|
||||
if (mCallback != null) {
|
||||
if (Song.getId(oldPrevious) != Song.getId(previous))
|
||||
mCallback.songReplaced(-1, previous);
|
||||
if (Song.getId(oldNext) != Song.getId(next))
|
||||
mCallback.songReplaced(1, next);
|
||||
}
|
||||
if (Song.getId(oldCurrent) != Song.getId(current)) {
|
||||
if (mCallback != null)
|
||||
mCallback.songReplaced(0, current);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user