Support for Android Marshmallow's permission system

This commit is contained in:
Karim Abou Zeid 2015-08-18 23:47:23 +02:00
commit 6f3617650a
12 changed files with 305 additions and 176 deletions

View file

@ -28,6 +28,8 @@
<h3>Version 0.9.43 beta5</h3> <h3>Version 0.9.43 beta5</h3>
<ol> <ol>
<li><b>NEW:</b> Support for Android Marshmallow's permission system.</a>.
</li>
<li><b>IMPROVEMENT:</b> The sliding panel should open and close a bit smoother now.</a>. <li><b>IMPROVEMENT:</b> The sliding panel should open and close a bit smoother now.</a>.
</li> </li>
</ol> </ol>

View file

@ -77,29 +77,36 @@ public class AlbumLoader {
return new Album(id, albumName, artist, artistId, songCount, year); return new Album(id, albumName, artist, artistId, songCount, year);
} }
@Nullable
public static Cursor makeAlbumCursor(@NonNull final Context context, final String selection, final String[] values) { public static Cursor makeAlbumCursor(@NonNull final Context context, final String selection, final String[] values) {
return makeAlbumCursor(context, selection, values, PreferenceUtil.getInstance(context).getAlbumSortOrder()); 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) { 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); 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) { 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, try {
new String[]{ return context.getContentResolver().query(contentUri,
new String[]{
/* 0 */ /* 0 */
BaseColumns._ID, BaseColumns._ID,
/* 1 */ /* 1 */
AlbumColumns.ALBUM, AlbumColumns.ALBUM,
/* 2 */ /* 2 */
AlbumColumns.ARTIST, AlbumColumns.ARTIST,
/* 3 */ /* 3 */
AudioColumns.ARTIST_ID, AudioColumns.ARTIST_ID,
/* 4 */ /* 4 */
AlbumColumns.NUMBER_OF_SONGS, AlbumColumns.NUMBER_OF_SONGS,
/* 5 */ /* 5 */
AlbumColumns.FIRST_YEAR, AlbumColumns.FIRST_YEAR,
}, selection, values, sortOrder); }, selection, values, sortOrder);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -21,11 +21,15 @@ public class ArtistAlbumLoader {
} }
public static Cursor makeArtistAlbumCursor(@NonNull final Context context, final int artistId) { public static Cursor makeArtistAlbumCursor(@NonNull final Context context, final int artistId) {
return AlbumLoader.makeAlbumCursor(context, try {
MediaStore.Audio.Artists.Albums.getContentUri("external", artistId), return AlbumLoader.makeAlbumCursor(context,
null, MediaStore.Audio.Artists.Albums.getContentUri("external", artistId),
null, null,
PreferenceUtil.getInstance(context).getArtistAlbumSortOrder() null,
); PreferenceUtil.getInstance(context).getArtistAlbumSortOrder()
);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -74,17 +74,22 @@ public class ArtistLoader {
return new Artist(id, artistName, albumCount, songCount); return new Artist(id, artistName, albumCount, songCount);
} }
@Nullable
public static Cursor makeArtistCursor(@NonNull final Context context, final String selection, final String[] values) { public static Cursor makeArtistCursor(@NonNull final Context context, final String selection, final String[] values) {
return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, try {
new String[]{ return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */ /* 0 */
BaseColumns._ID, BaseColumns._ID,
/* 1 */ /* 1 */
ArtistColumns.ARTIST, ArtistColumns.ARTIST,
/* 2 */ /* 2 */
ArtistColumns.NUMBER_OF_ALBUMS, ArtistColumns.NUMBER_OF_ALBUMS,
/* 3 */ /* 3 */
ArtistColumns.NUMBER_OF_TRACKS ArtistColumns.NUMBER_OF_TRACKS
}, selection, values, PreferenceUtil.getInstance(context).getArtistSortOrder()); }, selection, values, PreferenceUtil.getInstance(context).getArtistSortOrder());
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -21,13 +21,17 @@ public class ArtistSongLoader {
} }
public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) { public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) {
return SongLoader.makeSongCursor( try {
context, return SongLoader.makeSongCursor(
MediaStore.Audio.AudioColumns.ARTIST_ID + "=?", context,
new String[]{ MediaStore.Audio.AudioColumns.ARTIST_ID + "=?",
String.valueOf(artistId) new String[]{
}, String.valueOf(artistId)
PreferenceUtil.getInstance(context).getArtistSongSortOrder() },
); PreferenceUtil.getInstance(context).getArtistSongSortOrder()
);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -75,13 +75,18 @@ public class PlaylistLoader {
return new Playlist(id, name); return new Playlist(id, name);
} }
@Nullable
public static Cursor makePlaylistCursor(@NonNull final Context context, final String selection, final String[] values) { public static Cursor makePlaylistCursor(@NonNull final Context context, final String selection, final String[] values) {
return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, try {
new String[]{ return context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */ /* 0 */
BaseColumns._ID, BaseColumns._ID,
/* 1 */ /* 1 */
PlaylistsColumns.NAME PlaylistsColumns.NAME
}, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER); }, selection, values, MediaStore.Audio.Playlists.DEFAULT_SORT_ORDER);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -45,30 +45,34 @@ public class PlaylistSongLoader {
} }
public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) { public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) {
return context.getContentResolver().query( try {
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), return context.getContentResolver().query(
new String[]{ MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
new String[]{
/* 0 */ /* 0 */
MediaStore.Audio.Playlists.Members.AUDIO_ID, MediaStore.Audio.Playlists.Members.AUDIO_ID,
/* 1 */ /* 1 */
AudioColumns.TITLE, AudioColumns.TITLE,
/* 2 */ /* 2 */
AudioColumns.ARTIST, AudioColumns.ARTIST,
/* 3 */ /* 3 */
AudioColumns.ALBUM, AudioColumns.ALBUM,
/* 4 */ /* 4 */
AudioColumns.DURATION, AudioColumns.DURATION,
/* 5 */ /* 5 */
AudioColumns.TRACK, AudioColumns.TRACK,
/* 6 */ /* 6 */
AudioColumns.ALBUM_ID, AudioColumns.ALBUM_ID,
/* 7 */ /* 7 */
AudioColumns.ARTIST_ID, AudioColumns.ARTIST_ID,
/* 8 */ /* 8 */
AudioColumns.DATA, AudioColumns.DATA,
/* 9 */ /* 9 */
MediaStore.Audio.Playlists.Members._ID MediaStore.Audio.Playlists.Members._ID
}, SongLoader.BASE_SELECTION, null, }, SongLoader.BASE_SELECTION, null,
MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER); MediaStore.Audio.Playlists.Members.DEFAULT_SORT_ORDER);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -13,6 +13,8 @@ import com.kabouzeid.gramophone.util.PreferenceUtil;
import java.util.ArrayList; import java.util.ArrayList;
import hugo.weaving.DebugLog;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
@ -77,36 +79,43 @@ public class SongLoader {
return new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber, data); 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) { public static Cursor makeSongCursor(@NonNull final Context context, final String selection, final String[] values) {
return makeSongCursor(context, selection, values, PreferenceUtil.getInstance(context).getSongSortOrder()); 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) { public static Cursor makeSongCursor(@NonNull final Context context, @Nullable final String selection, final String[] values, final String sortOrder) {
String baseSelection = BASE_SELECTION; String baseSelection = BASE_SELECTION;
if (selection != null && !selection.trim().equals("")) { if (selection != null && !selection.trim().equals("")) {
baseSelection += " AND " + selection; baseSelection += " AND " + selection;
} }
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, try {
new String[]{ return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */ /* 0 */
BaseColumns._ID, BaseColumns._ID,
/* 1 */ /* 1 */
AudioColumns.TITLE, AudioColumns.TITLE,
/* 2 */ /* 2 */
AudioColumns.ARTIST, AudioColumns.ARTIST,
/* 3 */ /* 3 */
AudioColumns.ALBUM, AudioColumns.ALBUM,
/* 4 */ /* 4 */
AudioColumns.DURATION, AudioColumns.DURATION,
/* 5 */ /* 5 */
AudioColumns.TRACK, AudioColumns.TRACK,
/* 6 */ /* 6 */
AudioColumns.ARTIST_ID, AudioColumns.ARTIST_ID,
/* 7 */ /* 7 */
AudioColumns.ALBUM_ID, AudioColumns.ALBUM_ID,
/* 8 */ /* 8 */
AudioColumns.DATA AudioColumns.DATA
}, baseSelection, values, sortOrder); }, baseSelection, values, sortOrder);
} catch (SecurityException e) {
return null;
}
} }
} }

View file

@ -4,7 +4,6 @@ import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.ContentUris;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
@ -139,7 +138,7 @@ public class MusicService extends Service implements SharedPreferences.OnSharedP
private boolean isServiceInUse; private boolean isServiceInUse;
private static String getTrackUri(@NonNull Song song) { 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 @Override

View file

@ -1,14 +1,19 @@
package com.kabouzeid.gramophone.ui.activities.base; package com.kabouzeid.gramophone.ui.activities.base;
import android.Manifest;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.widget.Toast;
import com.kabouzeid.gramophone.helper.MusicPlayerRemote; import com.kabouzeid.gramophone.helper.MusicPlayerRemote;
import com.kabouzeid.gramophone.interfaces.MusicServiceEventListener; import com.kabouzeid.gramophone.interfaces.MusicServiceEventListener;
@ -17,24 +22,53 @@ import com.kabouzeid.gramophone.service.MusicService;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import hugo.weaving.DebugLog;
/** /**
* @author Karim Abou Zeid (kabouzeid) * @author Karim Abou Zeid (kabouzeid)
*/ */
public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements ServiceConnection, MusicServiceEventListener { public abstract class AbsMusicServiceActivity extends AbsBaseActivity implements ServiceConnection, MusicServiceEventListener {
public static final String TAG = AbsMusicServiceActivity.class.getSimpleName(); public static final String TAG = AbsMusicServiceActivity.class.getSimpleName();
public static final int REQUEST_EXTERNAL_STORAGE_PERMISSION = 0;
private final ArrayList<MusicServiceEventListener> mMusicServiceEventListener = new ArrayList<>(); private final ArrayList<MusicServiceEventListener> mMusicServiceEventListener = new ArrayList<>();
private MusicPlayerRemote.ServiceToken serviceToken; private MusicPlayerRemote.ServiceToken serviceToken;
private MusicStateReceiver musicStateReceiver; private MusicStateReceiver musicStateReceiver;
private boolean receiverRegistered; private boolean receiverRegistered;
private boolean hasExternalStoragePermission;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
checkExternalStoragePermissions();
serviceToken = MusicPlayerRemote.bindToService(this, this); 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 @Override
public void onServiceConnected(ComponentName name, IBinder service) { public void onServiceConnected(ComponentName name, IBinder service) {
if (!receiverRegistered) { 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();
}
}
} }

View file

@ -86,22 +86,25 @@ public class MusicUtil {
return; 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 { try {
if (cursor != null && cursor.getCount() == 1) { Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
cursor.moveToFirst(); new String[]{MediaStore.MediaColumns.TITLE},
Settings.System.putString(resolver, Settings.System.RINGTONE, uri.toString()); BaseColumns._ID + "=?",
final String message = context.getString(R.string.x_has_been_set_as_ringtone, cursor.getString(0)); new String[]{String.valueOf(id)},
Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); null);
} try {
} finally { if (cursor != null && cursor.getCount() == 1) {
if (cursor != null) { cursor.moveToFirst();
cursor.close(); 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(")"); 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 try {
context.getContentResolver().delete(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, final Cursor cursor = context.getContentResolver().query(
selection.toString(), null); MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null);
// Step 3: Remove files from card if (cursor != null) {
cursor.moveToFirst(); // Step 1: Remove selected tracks from the current playlist, as well
while (!cursor.isAfterLast()) { // as from the album art cache
final String name = cursor.getString(1); cursor.moveToFirst();
try { // File.delete can throw a security exception while (!cursor.isAfterLast()) {
final File f = new File(name); final int id = cursor.getInt(0);
if (!f.delete()) { final Song song = SongLoader.getSong(context, id);
// I'm not sure if we'd ever get here (deletion would MusicPlayerRemote.removeFromQueue(song);
// have to fail, but no exception thrown)
Log.e("MusicUtils", "Failed to delete file " + name);
}
cursor.moveToNext(); 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) { public static Playlist getFavoritesPlaylist(@NonNull final Context context) {

View file

@ -3,7 +3,6 @@ package com.kabouzeid.gramophone.util;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.BaseColumns; import android.provider.BaseColumns;
@ -16,7 +15,6 @@ import com.kabouzeid.gramophone.R;
import com.kabouzeid.gramophone.model.Playlist; import com.kabouzeid.gramophone.model.Playlist;
import com.kabouzeid.gramophone.model.PlaylistSong; import com.kabouzeid.gramophone.model.PlaylistSong;
import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.model.Song;
import com.kabouzeid.gramophone.service.MusicService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -29,28 +27,31 @@ public class PlaylistsUtil {
public static int createPlaylist(@NonNull final Context context, @Nullable final String name) { public static int createPlaylist(@NonNull final Context context, @Nullable final String name) {
int id = -1; int id = -1;
if (name != null && name.length() > 0) { if (name != null && name.length() > 0) {
Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, try {
new String[]{MediaStore.Audio.Playlists._ID}, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, null); Cursor cursor = context.getContentResolver().query(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
if (cursor == null || cursor.getCount() < 1) { new String[]{MediaStore.Audio.Playlists._ID}, MediaStore.Audio.PlaylistsColumns.NAME + "=?", new String[]{name}, null);
final ContentValues values = new ContentValues(1); if (cursor == null || cursor.getCount() < 1) {
values.put(MediaStore.Audio.PlaylistsColumns.NAME, name); final ContentValues values = new ContentValues(1);
final Uri uri = context.getContentResolver().insert( values.put(MediaStore.Audio.PlaylistsColumns.NAME, name);
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, final Uri uri = context.getContentResolver().insert(
values); MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
if (uri != null) { values);
// necessary because somehow the MediaStoreObserver is not notified when adding a playlist if (uri != null) {
context.sendBroadcast(new Intent(MusicService.MEDIA_STORE_CHANGED)); // necessary because somehow the MediaStoreObserver is not notified when adding a playlist
Toast.makeText(context, context.getResources().getString( context.getContentResolver().notifyChange(Uri.parse("content://media"), null);
R.string.created_playlist_x, name), Toast.LENGTH_SHORT).show(); Toast.makeText(context, context.getResources().getString(
id = Integer.parseInt(uri.getLastPathSegment()); 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 != null) {
if (cursor.moveToFirst()) { cursor.close();
id = cursor.getInt(cursor.getColumnIndex(MediaStore.Audio.Playlists._ID));
} }
} } catch (SecurityException ignored) {
if (cursor != null) {
cursor.close();
} }
} }
if (id == -1) { if (id == -1) {
@ -70,7 +71,10 @@ public class PlaylistsUtil {
} }
} }
selection.append(")"); 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) { 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; int base = 0;
try { try {
cursor = resolver.query(uri, projection, null, null, null); try {
cursor = resolver.query(uri, projection, null, null, null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
base = cursor.getInt(0) + 1; base = cursor.getInt(0) + 1;
}
} finally {
if (cursor != null) {
cursor.close();
}
} }
} finally {
if (cursor != null) { int numInserted = 0;
cursor.close(); 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) {
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();
} }
} }
@ -133,7 +140,10 @@ public class PlaylistsUtil {
String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?"; String selection = MediaStore.Audio.Playlists.Members.AUDIO_ID + " =?";
String[] selectionArgs = new String[]{String.valueOf(song.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<PlaylistSong> songs) { public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List<PlaylistSong> songs) {
@ -145,23 +155,30 @@ public class PlaylistsUtil {
selectionArgs[i] = String.valueOf(songs.get(i).idInPlayList); selectionArgs[i] = String.valueOf(songs.get(i).idInPlayList);
} }
String selection = MediaStore.Audio.Playlists.Members._ID + " in ("; String selection = MediaStore.Audio.Playlists.Members._ID + " in (";
//noinspection unused
for (String selectionArg : selectionArgs) selection += "?, "; for (String selectionArg : selectionArgs) selection += "?, ";
selection = selection.substring(0, selection.length() - 2) + ")"; 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) { public static boolean doPlaylistContains(@NonNull final Context context, final long playlistId, final int songId) {
if (playlistId != -1) { if (playlistId != -1) {
Cursor c = context.getContentResolver().query( try {
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId), Cursor c = context.getContentResolver().query(
new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null); MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
int count = 0; new String[]{MediaStore.Audio.Playlists.Members.AUDIO_ID}, MediaStore.Audio.Playlists.Members.AUDIO_ID + "=?", new String[]{String.valueOf(songId)}, null);
if (c != null) { int count = 0;
count = c.getCount(); if (c != null) {
c.close(); count = c.getCount();
c.close();
}
return count > 0;
} catch (SecurityException ignored) {
} }
return count > 0;
} }
return false; return false;
} }
@ -174,27 +191,33 @@ public class PlaylistsUtil {
public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) { public static void renamePlaylist(@NonNull final Context context, final long id, final String newName) {
ContentValues contentValues = new ContentValues(); ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName); contentValues.put(MediaStore.Audio.PlaylistsColumns.NAME, newName);
context.getContentResolver().update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, try {
contentValues, context.getContentResolver().update(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
MediaStore.Audio.Playlists._ID + "=?", contentValues,
new String[]{String.valueOf(id)}); MediaStore.Audio.Playlists._ID + "=?",
new String[]{String.valueOf(id)});
} catch (SecurityException ignored) {
}
} }
public static String getNameForPlaylist(@NonNull final Context context, final long id) { public static String getNameForPlaylist(@NonNull final Context context, final long id) {
Cursor cursor = context.getContentResolver().query( try {
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI, Cursor cursor = context.getContentResolver().query(
new String[]{MediaStore.Audio.PlaylistsColumns.NAME}, MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
BaseColumns._ID + "=?", new String[]{MediaStore.Audio.PlaylistsColumns.NAME},
new String[]{String.valueOf(id)}, BaseColumns._ID + "=?",
null); new String[]{String.valueOf(id)},
if (cursor != null) { null);
try { if (cursor != null) {
if (cursor.moveToFirst()) { try {
return cursor.getString(0); if (cursor.moveToFirst()) {
return cursor.getString(0);
}
} finally {
cursor.close();
} }
} finally {
cursor.close();
} }
} catch (SecurityException ignored) {
} }
return ""; return "";
} }