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>
<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>
</ol>

View file

@ -77,15 +77,19 @@ 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) {
try {
return context.getContentResolver().query(contentUri,
new String[]{
/* 0 */
@ -101,5 +105,8 @@ public class AlbumLoader {
/* 5 */
AlbumColumns.FIRST_YEAR,
}, 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) {
try {
return AlbumLoader.makeAlbumCursor(context,
MediaStore.Audio.Artists.Albums.getContentUri("external", artistId),
null,
null,
PreferenceUtil.getInstance(context).getArtistAlbumSortOrder()
);
} catch (SecurityException e) {
return null;
}
}
}

View file

@ -74,7 +74,9 @@ 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) {
try {
return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
@ -86,5 +88,8 @@ public class ArtistLoader {
/* 3 */
ArtistColumns.NUMBER_OF_TRACKS
}, selection, values, PreferenceUtil.getInstance(context).getArtistSortOrder());
} catch (SecurityException e) {
return null;
}
}
}

View file

@ -21,6 +21,7 @@ public class ArtistSongLoader {
}
public static Cursor makeArtistSongCursor(@NonNull final Context context, final int artistId) {
try {
return SongLoader.makeSongCursor(
context,
MediaStore.Audio.AudioColumns.ARTIST_ID + "=?",
@ -29,5 +30,8 @@ public class ArtistSongLoader {
},
PreferenceUtil.getInstance(context).getArtistSongSortOrder()
);
} catch (SecurityException e) {
return null;
}
}
}

View file

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

View file

@ -45,6 +45,7 @@ public class PlaylistSongLoader {
}
public static Cursor makePlaylistSongCursor(@NonNull final Context context, final int playlistId) {
try {
return context.getContentResolver().query(
MediaStore.Audio.Playlists.Members.getContentUri("external", playlistId),
new String[]{
@ -70,5 +71,8 @@ public class PlaylistSongLoader {
MediaStore.Audio.Playlists.Members._ID
}, SongLoader.BASE_SELECTION, null,
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 hugo.weaving.DebugLog;
/**
* @author Karim Abou Zeid (kabouzeid)
*/
@ -77,16 +79,20 @@ 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;
}
try {
return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{
/* 0 */
@ -108,5 +114,8 @@ public class SongLoader {
/* 8 */
AudioColumns.DATA
}, baseSelection, values, sortOrder);
} catch (SecurityException e) {
return null;
}
}
}

View file

@ -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

View file

@ -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<MusicServiceEventListener> 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();
}
}
}

View file

@ -86,6 +86,7 @@ public class MusicUtil {
return;
}
try {
Cursor cursor = resolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.MediaColumns.TITLE},
BaseColumns._ID + "=?",
@ -103,6 +104,8 @@ public class MusicUtil {
cursor.close();
}
}
} catch (SecurityException ignored) {
}
}
@NonNull
@ -174,6 +177,8 @@ public class MusicUtil {
}
}
selection.append(")");
try {
final Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null);
@ -214,6 +219,8 @@ public class MusicUtil {
}
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) {
}
}
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.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,6 +27,7 @@ public class PlaylistsUtil {
public static int createPlaylist(@NonNull final Context context, @Nullable final String name) {
int id = -1;
if (name != null && name.length() > 0) {
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) {
@ -39,7 +38,7 @@ public class PlaylistsUtil {
values);
if (uri != null) {
// necessary because somehow the MediaStoreObserver is not notified when adding a playlist
context.sendBroadcast(new Intent(MusicService.MEDIA_STORE_CHANGED));
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());
@ -52,6 +51,8 @@ public class PlaylistsUtil {
if (cursor != null) {
cursor.close();
}
} catch (SecurityException ignored) {
}
}
if (id == -1) {
Toast.makeText(context, context.getResources().getString(
@ -70,7 +71,10 @@ public class PlaylistsUtil {
}
}
selection.append(")");
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) {
@ -89,6 +93,7 @@ public class PlaylistsUtil {
Cursor cursor = null;
int base = 0;
try {
try {
cursor = resolver.query(uri, projection, null, null, null);
@ -101,13 +106,15 @@ public class PlaylistsUtil {
}
}
int numinserted = 0;
int numInserted = 0;
for (int offSet = 0; offSet < size; offSet += 1000)
numinserted += resolver.bulkInsert(uri, makeInsertItems(songs, offSet, 1000, base));
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();
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)};
try {
context.getContentResolver().delete(uri, selection, selectionArgs);
} catch (SecurityException ignored) {
}
}
public static void removeFromPlaylist(@NonNull final Context context, @NonNull final List<PlaylistSong> songs) {
@ -145,14 +155,19 @@ 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) + ")";
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) {
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);
@ -162,6 +177,8 @@ public class PlaylistsUtil {
c.close();
}
return count > 0;
} catch (SecurityException ignored) {
}
}
return false;
}
@ -174,13 +191,17 @@ 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);
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) {
try {
Cursor cursor = context.getContentResolver().query(
MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Audio.PlaylistsColumns.NAME},
@ -196,6 +217,8 @@ public class PlaylistsUtil {
cursor.close();
}
}
} catch (SecurityException ignored) {
}
return "";
}
}