diff --git a/app/src/main/assets/changelog.html b/app/src/main/assets/changelog.html index 19218355..5a8a31ea 100644 --- a/app/src/main/assets/changelog.html +++ b/app/src/main/assets/changelog.html @@ -28,6 +28,8 @@

Version 0.9.43 beta5

    +
  1. NEW: Support for Android Marshmallow's permission system.. +
  2. IMPROVEMENT: The sliding panel should open and close a bit smoother now..
diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java index 5400ea72..841e6919 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java @@ -77,29 +77,36 @@ public class AlbumLoader { return new Album(id, albumName, artist, artistId, songCount, year); } + @Nullable public static Cursor makeAlbumCursor(@NonNull final Context context, final String selection, final String[] values) { return makeAlbumCursor(context, selection, values, PreferenceUtil.getInstance(context).getAlbumSortOrder()); } + @Nullable public static Cursor makeAlbumCursor(@NonNull final Context context, final String selection, final String[] values, final String sortOrder) { return makeAlbumCursor(context, MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, selection, values, sortOrder); } + @Nullable public static Cursor makeAlbumCursor(@NonNull final Context context, @NonNull final Uri contentUri, final String selection, final String[] values, final String sortOrder) { - return context.getContentResolver().query(contentUri, - new String[]{ + try { + return context.getContentResolver().query(contentUri, + new String[]{ /* 0 */ - BaseColumns._ID, + BaseColumns._ID, /* 1 */ - AlbumColumns.ALBUM, + AlbumColumns.ALBUM, /* 2 */ - AlbumColumns.ARTIST, + AlbumColumns.ARTIST, /* 3 */ - AudioColumns.ARTIST_ID, + AudioColumns.ARTIST_ID, /* 4 */ - AlbumColumns.NUMBER_OF_SONGS, + AlbumColumns.NUMBER_OF_SONGS, /* 5 */ - AlbumColumns.FIRST_YEAR, - }, selection, values, sortOrder); + AlbumColumns.FIRST_YEAR, + }, selection, values, sortOrder); + } catch (SecurityException e) { + return null; + } } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistAlbumLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistAlbumLoader.java index d2382960..d5d04ac9 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistAlbumLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistAlbumLoader.java @@ -21,11 +21,15 @@ public class ArtistAlbumLoader { } public static Cursor makeArtistAlbumCursor(@NonNull final Context context, final int artistId) { - return AlbumLoader.makeAlbumCursor(context, - MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), - null, - null, - PreferenceUtil.getInstance(context).getArtistAlbumSortOrder() - ); + try { + return AlbumLoader.makeAlbumCursor(context, + MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), + null, + null, + PreferenceUtil.getInstance(context).getArtistAlbumSortOrder() + ); + } catch (SecurityException e) { + return null; + } } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java index 67d213f6..b261e3ac 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java @@ -74,17 +74,22 @@ public class ArtistLoader { return new Artist(id, artistName, albumCount, songCount); } + @Nullable public static Cursor makeArtistCursor(@NonNull final Context context, final String selection, final String[] values) { - return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, - new String[]{ + try { + return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, + new String[]{ /* 0 */ - BaseColumns._ID, + BaseColumns._ID, /* 1 */ - ArtistColumns.ARTIST, + ArtistColumns.ARTIST, /* 2 */ - ArtistColumns.NUMBER_OF_ALBUMS, + ArtistColumns.NUMBER_OF_ALBUMS, /* 3 */ - ArtistColumns.NUMBER_OF_TRACKS - }, selection, values, PreferenceUtil.getInstance(context).getArtistSortOrder()); + ArtistColumns.NUMBER_OF_TRACKS + }, selection, values, PreferenceUtil.getInstance(context).getArtistSortOrder()); + } catch (SecurityException e) { + return null; + } } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistSongLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistSongLoader.java index 76a78a14..e2eba644 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistSongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistSongLoader.java @@ -21,13 +21,17 @@ public class ArtistSongLoader { } public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) { - return SongLoader.makeSongCursor( - context, - MediaStore.Audio.AudioColumns.ARTIST_ID + "=?", - new String[]{ - String.valueOf(artistId) - }, - PreferenceUtil.getInstance(context).getArtistSongSortOrder() - ); + try { + return SongLoader.makeSongCursor( + context, + MediaStore.Audio.AudioColumns.ARTIST_ID + "=?", + new String[]{ + String.valueOf(artistId) + }, + PreferenceUtil.getInstance(context).getArtistSongSortOrder() + ); + } catch (SecurityException e) { + return null; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistLoader.java index 69397cc2..8bc95b16 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistLoader.java @@ -75,13 +75,18 @@ public class PlaylistLoader { return new Playlist(id, name); } + @Nullable public static Cursor makePlaylistCursor(@NonNull final Context context, final String selection, final String[] values) { - return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, - new String[]{ + try { + return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + new String[]{ /* 0 */ - BaseColumns._ID, + BaseColumns._ID, /* 1 */ - PlaylistsColumns.NAME - }, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); + PlaylistsColumns.NAME + }, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); + } catch (SecurityException e) { + return null; + } } } \ No newline at end of file diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistSongLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistSongLoader.java index ef4e9fe7..6ff4dedb 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistSongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/PlaylistSongLoader.java @@ -45,30 +45,34 @@ public class PlaylistSongLoader { } public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) { - return context.getContentResolver().query( - MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), - new String[]{ + try { + return context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{ /* 0 */ - MediaStore.Audio.Playlists.Members.AUDIO_ID, + MediaStore.Audio.Playlists.Members.AUDIO_ID, /* 1 */ - AudioColumns.TITLE, + AudioColumns.TITLE, /* 2 */ - AudioColumns.ARTIST, + AudioColumns.ARTIST, /* 3 */ - AudioColumns.ALBUM, + AudioColumns.ALBUM, /* 4 */ - AudioColumns.DURATION, + AudioColumns.DURATION, /* 5 */ - AudioColumns.TRACK, + AudioColumns.TRACK, /* 6 */ - AudioColumns.ALBUM_ID, + AudioColumns.ALBUM_ID, /* 7 */ - AudioColumns.ARTIST_ID, + AudioColumns.ARTIST_ID, /* 8 */ - AudioColumns.DATA, + AudioColumns.DATA, /* 9 */ - MediaStore.Audio.Playlists.Members._ID - }, SongLoader.BASE_SELECTION, null, - MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); + MediaStore.Audio.Playlists.Members._ID + }, SongLoader.BASE_SELECTION, null, + MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); + } catch (SecurityException e) { + return null; + } } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java index 18124dfd..7db01603 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java @@ -13,6 +13,8 @@ import com.kabouzeid.gramophone.util.PreferenceUtil; import java.util.ArrayList; +import hugo.weaving.DebugLog; + /** * @author Karim Abou Zeid (kabouzeid) */ @@ -77,36 +79,43 @@ public class SongLoader { return new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber, data); } + @Nullable public static Cursor makeSongCursor(@NonNull final Context context, final String selection, final String[] values) { return makeSongCursor(context, selection, values, PreferenceUtil.getInstance(context).getSongSortOrder()); } + @DebugLog + @Nullable public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] values, final String sortOrder) { String baseSelection = BASE_SELECTION; if (selection != null && !selection.trim().equals("")) { baseSelection += " AND " + selection; } - return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - new String[]{ + try { + return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{ /* 0 */ - BaseColumns._ID, + BaseColumns._ID, /* 1 */ - AudioColumns.TITLE, + AudioColumns.TITLE, /* 2 */ - AudioColumns.ARTIST, + AudioColumns.ARTIST, /* 3 */ - AudioColumns.ALBUM, + AudioColumns.ALBUM, /* 4 */ - AudioColumns.DURATION, + AudioColumns.DURATION, /* 5 */ - AudioColumns.TRACK, + AudioColumns.TRACK, /* 6 */ - AudioColumns.ARTIST_ID, + AudioColumns.ARTIST_ID, /* 7 */ - AudioColumns.ALBUM_ID, + AudioColumns.ALBUM_ID, /* 8 */ - AudioColumns.DATA - }, baseSelection, values, sortOrder); + AudioColumns.DATA + }, baseSelection, values, sortOrder); + } catch (SecurityException e) { + return null; + } } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java index 2867a94e..c619fe2c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -4,7 +4,6 @@ import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -139,7 +138,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP private boolean isServiceInUse; private static String getTrackUri(@NonNull Song song) { - return ContentUris.withAppendedId(android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, song.id).toString(); + return MusicUtil.getSongUri(song.id).toString(); } @Override diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java index 92972e40..58617f4f 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/base/AbsMusicServiceActivity.java @@ -1,14 +1,19 @@ package com.kabouzeid.gramophone.ui.activities.base; +import android.Manifest; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; import android.support.annotation.NonNull; +import android.widget.Toast; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.interfaces.MusicServiceEventListener; @@ -17,24 +22,53 @@ import com.kabouzeid.gramophone.service.MusicService; import java.lang.ref.WeakReference; import java.util.ArrayList; +import hugo.weaving.DebugLog; + /** * @author Karim Abou Zeid (kabouzeid) */ public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements ServiceConnection, MusicServiceEventListener { public static final String TAG = AbsMusicServiceActivity.class.getSimpleName(); + public static final int REQUEST_EXTERNAL_STORAGE_PERMISSION = 0; + private final ArrayList mMusicServiceEventListener = new ArrayList<>(); private MusicPlayerRemote.ServiceToken serviceToken; private MusicStateReceiver musicStateReceiver; private boolean receiverRegistered; + private boolean hasExternalStoragePermission; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + checkExternalStoragePermissions(); serviceToken = MusicPlayerRemote.bindToService(this, this); } + @Override + protected void onResume() { + super.onResume(); + // the handler is necessary to avoid "java.lang.RuntimeException: Performing pause of activity that is not resumed" + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + recreateIfPermissionsChanged(); + } + }, 200); + } + + protected void recreateIfPermissionsChanged() { + if (didPermissionsChanged()) { + recreate(); + } + } + + private boolean didPermissionsChanged() { + return hasExternalStoragePermission != hasExternalStoragePermission(); + } + @Override public void onServiceConnected(ComponentName name, IBinder service) { if (!receiverRegistered) { @@ -161,4 +195,30 @@ public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements } } } + + private void checkExternalStoragePermissions() { + hasExternalStoragePermission = hasExternalStoragePermission(); + if (hasExternalStoragePermission) { + requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE_PERMISSION); + } + } + + private boolean hasExternalStoragePermission() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED; + } + + @DebugLog + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == REQUEST_EXTERNAL_STORAGE_PERMISSION) { + for (int grantResult : grantResults) { + if (grantResult == PackageManager.PERMISSION_GRANTED) { + recreate(); + return; + } + } + Toast.makeText(AbsMusicServiceActivity.this, "You must grant permission to external storage in order to explore your music", Toast.LENGTH_SHORT).show(); + } + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java index 48d635fa..c9525ee3 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/MusicUtil.java @@ -86,22 +86,25 @@ public class MusicUtil { return; } - Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - new String[]{MediaStore.MediaColumns.TITLE}, - BaseColumns._ID + "=?", - new String[]{String.valueOf(id)}, - null); try { - if (cursor != null && cursor.getCount() == 1) { - cursor.moveToFirst(); - Settings.System.putString(resolver, Settings.System.RINGTONE, uri.toString()); - final String message = context.getString(R.string.x_has_been_set_as_ringtone, cursor.getString(0)); - Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); - } - } finally { - if (cursor != null) { - cursor.close(); + Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.MediaColumns.TITLE}, + BaseColumns._ID + "=?", + new String[]{String.valueOf(id)}, + null); + try { + if (cursor != null && cursor.getCount() == 1) { + cursor.moveToFirst(); + Settings.System.putString(resolver, Settings.System.RINGTONE, uri.toString()); + final String message = context.getString(R.string.x_has_been_set_as_ringtone, cursor.getString(0)); + Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); + } + } finally { + if (cursor != null) { + cursor.close(); + } } + } catch (SecurityException ignored) { } } @@ -174,46 +177,50 @@ public class MusicUtil { } } selection.append(")"); - final Cursor cursor = context.getContentResolver().query( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), - null, null); - if (cursor != null) { - // Step 1: Remove selected tracks from the current playlist, as well - // as from the album art cache - cursor.moveToFirst(); - while (!cursor.isAfterLast()) { - final int id = cursor.getInt(0); - final Song song = SongLoader.getSong(context, id); - MusicPlayerRemote.removeFromQueue(song); - cursor.moveToNext(); - } - // Step 2: Remove selected tracks from the database - context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, - selection.toString(), null); - - // Step 3: Remove files from card - cursor.moveToFirst(); - while (!cursor.isAfterLast()) { - final String name = cursor.getString(1); - try { // File.delete can throw a security exception - final File f = new File(name); - if (!f.delete()) { - // I'm not sure if we'd ever get here (deletion would - // have to fail, but no exception thrown) - Log.e("MusicUtils", "Failed to delete file " + name); - } + try { + final Cursor cursor = context.getContentResolver().query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(), + null, null); + if (cursor != null) { + // Step 1: Remove selected tracks from the current playlist, as well + // as from the album art cache + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + final int id = cursor.getInt(0); + final Song song = SongLoader.getSong(context, id); + MusicPlayerRemote.removeFromQueue(song); cursor.moveToNext(); - } catch (@NonNull final SecurityException ex) { - cursor.moveToNext(); - } catch (NullPointerException e) { - Log.e("MusicUtils", "Failed to find file " + name); } + + // Step 2: Remove selected tracks from the database + context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, + selection.toString(), null); + + // Step 3: Remove files from card + cursor.moveToFirst(); + while (!cursor.isAfterLast()) { + final String name = cursor.getString(1); + try { // File.delete can throw a security exception + final File f = new File(name); + if (!f.delete()) { + // I'm not sure if we'd ever get here (deletion would + // have to fail, but no exception thrown) + Log.e("MusicUtils", "Failed to delete file " + name); + } + cursor.moveToNext(); + } catch (@NonNull final SecurityException ex) { + cursor.moveToNext(); + } catch (NullPointerException e) { + Log.e("MusicUtils", "Failed to find file " + name); + } + } + cursor.close(); } - cursor.close(); + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + Toast.makeText(context, context.getString(R.string.deleted_x_songs, songs.size()), Toast.LENGTH_SHORT).show(); + } catch (SecurityException ignored) { } - context.getContentResolver().notifyChange(Uri.parse("content://media"), null); - Toast.makeText(context, context.getString(R.string.deleted_x_songs, songs.size()), Toast.LENGTH_SHORT).show(); } public static Playlist getFavoritesPlaylist(@NonNull final Context context) { diff --git a/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java b/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java index 8bb5ea54..faed12ae 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java +++ b/app/src/main/java/com/kabouzeid/gramophone/util/PlaylistsUtil.java @@ -3,7 +3,6 @@ package com.kabouzeid.gramophone.util; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; -import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; @@ -16,7 +15,6 @@ import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.Song; -import com.kabouzeid.gramophone.service.MusicService; import java.util.ArrayList; import java.util.List; @@ -29,28 +27,31 @@ public class PlaylistsUtil { public static int createPlaylist(@NonNull final Context context, @Nullable final String name) { int id = -1; if (name != null && name.length() > 0) { - Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Audio.Playlists._ID}, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, null); - if (cursor == null || cursor.getCount() < 1) { - final ContentValues values = new ContentValues(1); - values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); - final Uri uri = context.getContentResolver().insert( - MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, - values); - if (uri != null) { - // necessary because somehow the MediaStoreObserver is not notified when adding a playlist - context.sendBroadcast(new Intent(MusicService.MEDIA_STORE_CHANGED)); - Toast.makeText(context, context.getResources().getString( - R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show(); - id = Integer.parseInt(uri.getLastPathSegment()); + try { + Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Audio.Playlists._ID}, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, null); + if (cursor == null || cursor.getCount() < 1) { + final ContentValues values = new ContentValues(1); + values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); + final Uri uri = context.getContentResolver().insert( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + values); + if (uri != null) { + // necessary because somehow the MediaStoreObserver is not notified when adding a playlist + context.getContentResolver().notifyChange(Uri.parse("content://media"), null); + Toast.makeText(context, context.getResources().getString( + R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show(); + id = Integer.parseInt(uri.getLastPathSegment()); + } + } else { + if (cursor.moveToFirst()) { + id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID)); + } } - } else { - if (cursor.moveToFirst()) { - id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID)); + if (cursor != null) { + cursor.close(); } - } - if (cursor != null) { - cursor.close(); + } catch (SecurityException ignored) { } } if (id == -1) { @@ -70,7 +71,10 @@ public class PlaylistsUtil { } } selection.append(")"); - context.getContentResolver().delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, selection.toString(), null); + try { + context.getContentResolver().delete(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, selection.toString(), null); + } catch (SecurityException ignored) { + } } public static void addToPlaylist(@NonNull final Context context, final Song song, final int playlistId, final boolean showToastOnFinish) { @@ -90,24 +94,27 @@ public class PlaylistsUtil { int base = 0; try { - cursor = resolver.query(uri, projection, null, null, null); + try { + cursor = resolver.query(uri, projection, null, null, null); - if (cursor != null && cursor.moveToFirst()) { - base = cursor.getInt(0) + 1; + if (cursor != null && cursor.moveToFirst()) { + base = cursor.getInt(0) + 1; + } + } finally { + if (cursor != null) { + cursor.close(); + } } - } finally { - if (cursor != null) { - cursor.close(); + + int numInserted = 0; + for (int offSet = 0; offSet < size; offSet += 1000) + numInserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); + + if (showToastOnFinish) { + Toast.makeText(context, context.getResources().getString( + R.string.inserted_x_songs_into_playlist, numInserted), Toast.LENGTH_SHORT).show(); } - } - - int numinserted = 0; - for (int offSet = 0; offSet < size; offSet += 1000) - numinserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base)); - - if (showToastOnFinish) { - Toast.makeText(context, context.getResources().getString( - R.string.inserted_x_songs_into_playlist, numinserted), Toast.LENGTH_SHORT).show(); + } catch (SecurityException ignored) { } } @@ -133,7 +140,10 @@ public class PlaylistsUtil { String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; String[] selectionArgs = new String[]{String.valueOf(song.id)}; - context.getContentResolver().delete(uri, selection, selectionArgs); + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } } public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List songs) { @@ -145,23 +155,30 @@ public class PlaylistsUtil { selectionArgs[i] = String.valueOf(songs.get(i).idInPlayList); } String selection = MediaStore.Audio.Playlists.Members._ID + " in ("; + //noinspection unused for (String selectionArg : selectionArgs) selection += "?, "; selection = selection.substring(0, selection.length() - 2) + ")"; - context.getContentResolver().delete(uri, selection, selectionArgs); + try { + context.getContentResolver().delete(uri, selection, selectionArgs); + } catch (SecurityException ignored) { + } } public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final int songId) { if (playlistId != -1) { - Cursor c = context.getContentResolver().query( - MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), - new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null); - int count = 0; - if (c != null) { - count = c.getCount(); - c.close(); + try { + Cursor c = context.getContentResolver().query( + MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), + new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null); + int count = 0; + if (c != null) { + count = c.getCount(); + c.close(); + } + return count > 0; + } catch (SecurityException ignored) { } - return count > 0; } return false; } @@ -174,27 +191,33 @@ public class PlaylistsUtil { public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) { ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName); - context.getContentResolver().update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, - contentValues, - MediaStore.Audio.Playlists._ID + "=?", - new String[]{String.valueOf(id)}); + try { + context.getContentResolver().update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + contentValues, + MediaStore.Audio.Playlists._ID + "=?", + new String[]{String.valueOf(id)}); + } catch (SecurityException ignored) { + } } public static String getNameForPlaylist(@NonNull final Context context, final long id) { - Cursor cursor = context.getContentResolver().query( - MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, - new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, - BaseColumns._ID + "=?", - new String[]{String.valueOf(id)}, - null); - if (cursor != null) { - try { - if (cursor.moveToFirst()) { - return cursor.getString(0); + try { + Cursor cursor = context.getContentResolver().query( + MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, + new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, + BaseColumns._ID + "=?", + new String[]{String.valueOf(id)}, + null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); } - } finally { - cursor.close(); } + } catch (SecurityException ignored) { } return ""; }