diff --git a/app/build.gradle b/app/build.gradle index f09b20a4..6eee6967 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "com.kabouzeid.gramophone" minSdkVersion 16 targetSdkVersion 22 - versionCode 38 - versionName "0.9.22b dev-1" + versionCode 39 + versionName "0.9.23b dev-1" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9a872428..2aef90ab 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -31,6 +31,11 @@ + + + + + diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java b/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java index 4bb80317..6126f650 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/MusicPlayerRemote.java @@ -30,6 +30,7 @@ public class MusicPlayerRemote { private static final String TAG = MusicPlayerRemote.class.getSimpleName(); private static int position = -1; + private static boolean startAfterConnected = false; private static ArrayList playingQueue; private static ArrayList restoredOriginalQueue; @@ -44,6 +45,7 @@ public class MusicPlayerRemote { MusicService.MusicBinder binder = (MusicService.MusicBinder) service; musicService = binder.getService(); musicService.restorePreviousState(restoredOriginalQueue, playingQueue, position); + if(startAfterConnected) resumePlaying(); } @Override @@ -121,6 +123,8 @@ public class MusicPlayerRemote { public static void openQueue(final ArrayList playingQueue, final int startPosition, final boolean startPlaying) { MusicPlayerRemote.playingQueue = playingQueue; + position = startPosition; + startAfterConnected = startPlaying; if (musicService != null) { musicService.openQueue(MusicPlayerRemote.playingQueue, startPosition, startPlaying); } diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java index 78699339..f401cb3d 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/PlayingNotificationHelper.java @@ -5,17 +5,17 @@ package com.kabouzeid.gramophone.helper; */ import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.TaskStackBuilder; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v7.app.NotificationCompat; +import android.support.v4.app.NotificationCompat; import android.support.v7.graphics.Palette; +import android.view.View; +import android.widget.RemoteViews; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.model.Song; @@ -24,87 +24,199 @@ import com.kabouzeid.gramophone.ui.activities.MusicControllerActivity; import com.kabouzeid.gramophone.util.MusicUtil; import com.kabouzeid.gramophone.util.PreferenceUtils; import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.assist.ImageSize; +import com.nostra13.universalimageloader.core.assist.ViewScaleType; +import com.nostra13.universalimageloader.core.imageaware.NonViewAware; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; public class PlayingNotificationHelper { public static final String TAG = PlayingNotificationHelper.class.getSimpleName(); + public static final int NOTIFICATION_ID = 1337; - public static Notification buildNotification(final Context context, MediaSessionCompat.Token sessionToken, final Song song, final boolean isPlaying) { + private final MusicService service; - NotificationCompat.MediaStyle style = new NotificationCompat.MediaStyle() - .setMediaSession(sessionToken) - .setShowActionsInCompactView(0, 1, 2); + private final NotificationManager notificationManager; + private Notification notification = null; - style.setShowCancelButton(true); - style.setCancelButtonIntent(retrievePlaybackAction(context, 3)); + private RemoteViews notificationLayout; + private RemoteViews notificationLayoutExpanded; - Bitmap albumArt = ImageLoader.getInstance().loadImageSync(MusicUtil.getAlbumArtUri(song.albumId).toString()); - if (albumArt == null) { - albumArt = BitmapFactory.decodeResource(context.getResources(), R.drawable.default_album_art); - } - int notificationColor = PreferenceUtils.getInstance(context).coloredNotification() ? - Palette.from(albumArt).generate().getVibrantColor(Color.TRANSPARENT) : - Color.TRANSPARENT; + private Song currentSong; + private String currentAlbumArtUri; - return new NotificationCompat.Builder(context) - .setSmallIcon(R.drawable.ic_notification) - .setLargeIcon(albumArt) - .setContentIntent(getOpenMusicControllerPendingIntent(context)) - .setContentTitle(song.title) - .setContentText(song.artistName) - .setSubText(song.albumName) - .setWhen(0) - .setShowWhen(false) - .setStyle(style) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - - .addAction(R.drawable.ic_skip_previous_white_36dp, - "", - retrievePlaybackAction(context, 2)) - - .addAction(isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp, - "", - retrievePlaybackAction(context, 0)) - - .addAction(R.drawable.ic_skip_next_white_36dp, - "", - retrievePlaybackAction(context, 1)) - - .setOnlyAlertOnce(true) - .setColor(notificationColor) - .build(); + public PlayingNotificationHelper(final MusicService service) { + this.service = service; + notificationManager = (NotificationManager) service + .getSystemService(Context.NOTIFICATION_SERVICE); } - private static PendingIntent getOpenMusicControllerPendingIntent(final Context context) { - Intent result = new Intent(context, MusicControllerActivity.class); - TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context); + public void buildNotification(final Song song, final boolean isPlaying) { + currentSong = song; + notificationLayout = new RemoteViews(service.getPackageName(), + R.layout.notification_controller); + notificationLayoutExpanded = new RemoteViews(service.getPackageName(), + R.layout.notification_controller_big); + + notification = new NotificationCompat.Builder(service) + .setSmallIcon(R.drawable.ic_notification) + .setContentIntent(getOpenMusicControllerPendingIntent()) + .setCategory(NotificationCompat.CATEGORY_PROGRESS) + .setPriority(NotificationCompat.PRIORITY_MAX) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setContent(notificationLayout) + .build(); + + notification.bigContentView = notificationLayoutExpanded; + + setUpCollapsedLayout(); + setUpExpandedLayout(); + loadAlbumArt(); + setUpPlaybackActions(isPlaying); + setUpExpandedPlaybackActions(isPlaying); + + service.startForeground(NOTIFICATION_ID, notification); + } + + private PendingIntent getOpenMusicControllerPendingIntent() { + Intent result = new Intent(service, MusicControllerActivity.class); + TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(service); taskStackBuilder.addParentStack(MusicControllerActivity.class); taskStackBuilder.addNextIntent(result); return taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); } - private static PendingIntent retrievePlaybackAction(final Context context, final int which) { - String actionString = null; + private void setUpExpandedPlaybackActions(boolean isPlaying) { + notificationLayoutExpanded.setOnClickPendingIntent(R.id.action_play_pause, + retrievePlaybackActions(1)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.action_next, + retrievePlaybackActions(2)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.action_prev, + retrievePlaybackActions(3)); + + notificationLayoutExpanded.setOnClickPendingIntent(R.id.action_quit, + retrievePlaybackActions(4)); + + notificationLayoutExpanded.setImageViewResource(R.id.action_play_pause, + isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp); + } + + private void setUpPlaybackActions(boolean isPlaying) { + notificationLayout.setOnClickPendingIntent(R.id.action_play_pause, + retrievePlaybackActions(1)); + + notificationLayout.setOnClickPendingIntent(R.id.action_next, + retrievePlaybackActions(2)); + + notificationLayout.setOnClickPendingIntent(R.id.action_prev, + retrievePlaybackActions(3)); + + notificationLayout.setImageViewResource(R.id.action_play_pause, + isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp); + } + + private PendingIntent retrievePlaybackActions(final int which) { + Intent action; + PendingIntent pendingIntent; + final ComponentName serviceName = new ComponentName(service, MusicService.class); switch (which) { - case 0: - actionString = MusicService.ACTION_TOGGLE_PLAYBACK; - break; case 1: - actionString = MusicService.ACTION_SKIP; - break; + action = new Intent(MusicService.ACTION_TOGGLE_PLAYBACK); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 1, action, 0); + return pendingIntent; case 2: - actionString = MusicService.ACTION_REWIND; - break; + action = new Intent(MusicService.ACTION_SKIP); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 2, action, 0); + return pendingIntent; case 3: - actionString = MusicService.ACTION_QUIT; + action = new Intent(MusicService.ACTION_REWIND); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 3, action, 0); + return pendingIntent; + case 4: + action = new Intent(MusicService.ACTION_QUIT); + action.setComponent(serviceName); + pendingIntent = PendingIntent.getService(service, 4, action, 0); + return pendingIntent; + default: break; } - if (actionString != null) { - final ComponentName serviceName = new ComponentName(context, MusicService.class); - Intent actionIntent = new Intent(actionString); - actionIntent.setComponent(serviceName); - return PendingIntent.getService(context, 0, actionIntent, 0); - } return null; } + + private void setUpCollapsedLayout() { + if (currentSong != null) { + notificationLayout.setTextViewText(R.id.title, currentSong.title); + notificationLayout.setTextViewText(R.id.text, currentSong.artistName); + notificationLayout.setTextViewText(R.id.text2, currentSong.albumName); + } + } + + private void setUpExpandedLayout() { + if (currentSong != null) { + notificationLayoutExpanded.setTextViewText(R.id.title, currentSong.title); + notificationLayoutExpanded.setTextViewText(R.id.text, currentSong.artistName); + notificationLayoutExpanded.setTextViewText(R.id.text2, currentSong.albumName); + } + } + + private void loadAlbumArt() { + currentAlbumArtUri = MusicUtil.getAlbumArtUri(currentSong.albumId).toString(); + ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() { + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + if (currentAlbumArtUri.equals(imageUri)) + setAlbumArt(loadedImage); + } + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + if (currentAlbumArtUri.equals(imageUri)) + setAlbumArt(null); + } + }); + } + + private void setAlbumArt(Bitmap albumArt) { + int defaultColor = service.getResources().getColor(R.color.default_notification_color); + int newColor = defaultColor; + if (albumArt != null) { + notificationLayout.setImageViewBitmap(R.id.icon, albumArt); + notificationLayoutExpanded.setImageViewBitmap(R.id.icon, albumArt); + if (PreferenceUtils.getInstance(service).coloredNotification()) + newColor = Palette.from(albumArt).generate().getVibrantColor(defaultColor); + } else { + notificationLayout.setImageViewResource(R.id.icon, R.drawable.default_album_art); + notificationLayoutExpanded.setImageViewResource(R.id.icon, R.drawable.default_album_art); + } + + notificationLayout.setInt(R.id.root, "setBackgroundColor", newColor); + notificationLayoutExpanded.setInt(R.id.root, "setBackgroundColor", newColor); + notificationManager.notify(NOTIFICATION_ID, notification); + } + + public void killNotification() { + service.stopForeground(true); + notification = null; + } + + public void updatePlayState(final boolean isPlaying) { + if (notification == null || notificationManager == null) { + return; + } + if (notificationLayout != null) { + notificationLayout.setImageViewResource(R.id.action_play_pause, + isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp); + } + if (notificationLayoutExpanded != null) { + notificationLayoutExpanded.setImageViewResource(R.id.action_play_pause, + isPlaying ? R.drawable.ic_pause_white_36dp : R.drawable.ic_play_arrow_white_36dp); + } + notificationManager.notify(NOTIFICATION_ID, notification); + } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/helper/SearchQueryHelper.java b/app/src/main/java/com/kabouzeid/gramophone/helper/SearchQueryHelper.java new file mode 100644 index 00000000..097f0cff --- /dev/null +++ b/app/src/main/java/com/kabouzeid/gramophone/helper/SearchQueryHelper.java @@ -0,0 +1,90 @@ +package com.kabouzeid.gramophone.helper; + +import android.app.SearchManager; +import android.content.Context; +import android.os.Bundle; +import android.provider.MediaStore; + +import com.kabouzeid.gramophone.loader.SongLoader; +import com.kabouzeid.gramophone.model.Song; + +import java.util.ArrayList; + +/** + * @author Karim Abou Zeid (kabouzeid) + */ +public class SearchQueryHelper { + private static final String TITLE_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.TITLE + ") = ?"; + private static final String ALBUM_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ALBUM + ") = ?"; + private static final String ARTIST_SELECTION = "lower(" + MediaStore.Audio.AudioColumns.ARTIST + ") = ?"; + private static final String AND = " AND "; + + public static ArrayList getSongs(final Context context, final Bundle extras) { + final String query = extras.getString(SearchManager.QUERY, null); + final String artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null); + final String albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null); + final String titleName = extras.getString(MediaStore.EXTRA_MEDIA_TITLE, null); + + ArrayList songs = new ArrayList<>(); + + if (artistName != null && albumName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), albumName.toLowerCase(), titleName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + if (artistName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION + AND + TITLE_SELECTION, new String[]{artistName.toLowerCase(), titleName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + if (albumName != null && titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION + AND + TITLE_SELECTION, new String[]{albumName.toLowerCase(), titleName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + if (artistName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{artistName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + if (albumName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{albumName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + if (titleName != null) { + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{titleName.toLowerCase()})); + } + if (!songs.isEmpty()) { + return songs; + } + + + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ARTIST_SELECTION, new String[]{query.toLowerCase()})); + if (!songs.isEmpty()) { + return songs; + } + + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, ALBUM_SELECTION, new String[]{query.toLowerCase()})); + if (!songs.isEmpty()) { + return songs; + } + + songs = SongLoader.getSongs(SongLoader.makeSongCursor(context, TITLE_SELECTION, new String[]{query.toLowerCase()})); + if (!songs.isEmpty()) { + return songs; + } + + return SongLoader.getSongs(context, query); + } +} 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 24cdfd55..c3d4275c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/AlbumLoader.java @@ -17,12 +17,26 @@ import java.util.List; public class AlbumLoader { public static List getAllAlbums(Context context) { - Cursor cursor = makeAlbumCursor(context); + Cursor cursor = makeAlbumCursor(context, null, null); + return getAlbums(cursor); + } + + public static List getAlbums(Context context, String query) { + Cursor cursor = makeAlbumCursor(context, MediaStore.Audio.AlbumColumns.ALBUM + " LIKE ?", new String[]{"%" + query + "%"}); + return getAlbums(cursor); + } + + public static Album getAlbum(Context context, int albumId) { + Cursor cursor = makeAlbumCursor(context, BaseColumns._ID + "=?", new String[]{String.valueOf(albumId)}); + return getAlbum(cursor); + } + + public static List getAlbums(Cursor cursor) { List albums = new ArrayList<>(); if (cursor != null && cursor.moveToFirst()) { do { - final int id = cursor.getInt(0); final String albumName = cursor.getString(1); + final int id = cursor.getInt(0); final String artist = cursor.getString(2); final int artistId = cursor.getInt(3); final int songCount = cursor.getInt(4); @@ -38,8 +52,23 @@ public class AlbumLoader { return albums; } - public static Cursor makeAlbumCursor(final Context context) { - return makeAlbumCursor(context, null, null); + public static Album getAlbum(Cursor cursor) { + Album album = new Album(); + if (cursor != null && cursor.moveToFirst()) { + final int id = cursor.getInt(0); + final String albumName = cursor.getString(1); + final String artist = cursor.getString(2); + final int artistId = cursor.getInt(3); + final int songCount = cursor.getInt(4); + final int year = cursor.getInt(5); + + album = new Album(id, albumName, artist, artistId, songCount, year); + } + + if (cursor != null) { + cursor.close(); + } + return album; } public static Cursor makeAlbumCursor(final Context context, final String selection, final String[] values) { @@ -59,46 +88,4 @@ public class AlbumLoader { MediaStore.Audio.AlbumColumns.FIRST_YEAR, }, selection, values, PreferenceUtils.getInstance(context).getAlbumSortOrder()); } - - public static Album getAlbum(Context context, int albumId) { - Cursor cursor = makeAlbumCursor(context, BaseColumns._ID + "=?", new String[]{String.valueOf(albumId)}); - Album album = new Album(); - if (cursor != null && cursor.moveToFirst()) { - final int id = cursor.getInt(0); - final String albumName = cursor.getString(1); - final String artist = cursor.getString(2); - final int artistId = cursor.getInt(3); - final int songCount = cursor.getInt(4); - final int year = cursor.getInt(5); - - album = new Album(id, albumName, artist, artistId, songCount, year); - } - - if (cursor != null) { - cursor.close(); - } - return album; - } - - public static List getAlbums(Context context, String query) { - Cursor cursor = makeAlbumCursor(context, MediaStore.Audio.AlbumColumns.ALBUM + " LIKE ?", new String[]{"%" + query + "%"}); - List albums = new ArrayList<>(); - if (cursor != null && cursor.moveToFirst()) { - do { - final String albumName = cursor.getString(1); - final int id = cursor.getInt(0); - final String artist = cursor.getString(2); - final int artistId = cursor.getInt(3); - final int songCount = cursor.getInt(4); - final int year = cursor.getInt(5); - - final Album album = new Album(id, albumName, artist, artistId, songCount, year); - albums.add(album); - } while (cursor.moveToNext()); - } - if (cursor != null) { - cursor.close(); - } - return albums; - } } 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 645de99c..6dbc2d0c 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/ArtistLoader.java @@ -17,12 +17,26 @@ import java.util.List; public class ArtistLoader { public static List getAllArtists(Context context) { - Cursor cursor = makeArtistCursor(context); + Cursor cursor = makeArtistCursor(context, null, null); + return getArtists(cursor); + } + + public static List getArtists(Context context, String query) { + Cursor cursor = makeArtistCursor(context, MediaStore.Audio.ArtistColumns.ARTIST + " LIKE ?", new String[]{"%" + query + "%"}); + return getArtists(cursor); + } + + public static Artist getArtist(Context context, int artistId) { + Cursor cursor = makeArtistCursor(context, BaseColumns._ID + "=?", new String[]{String.valueOf(artistId)}); + return getArtist(cursor); + } + + public static List getArtists(Cursor cursor) { List artists = new ArrayList<>(); if (cursor != null && cursor.moveToFirst()) { do { - final int id = cursor.getInt(0); final String artistName = cursor.getString(1); + final int id = cursor.getInt(0); final int albumCount = cursor.getInt(2); final int songCount = cursor.getInt(3); @@ -30,31 +44,14 @@ public class ArtistLoader { artists.add(artist); } while (cursor.moveToNext()); } - if (cursor != null) + + if (cursor != null) { cursor.close(); + } return artists; } - public static Cursor makeArtistCursor(final Context context) { - return makeArtistCursor(context, null, null); - } - - public static Cursor makeArtistCursor(final Context context, final String selection, final String[] values) { - return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, - new String[]{ - /* 0 */ - BaseColumns._ID, - /* 1 */ - MediaStore.Audio.ArtistColumns.ARTIST, - /* 2 */ - MediaStore.Audio.ArtistColumns.NUMBER_OF_ALBUMS, - /* 3 */ - MediaStore.Audio.ArtistColumns.NUMBER_OF_TRACKS - }, selection, values, PreferenceUtils.getInstance(context).getArtistSortOrder()); - } - - public static Artist getArtist(Context context, int artistId) { - Cursor cursor = makeArtistCursor(context, BaseColumns._ID + "=?", new String[]{String.valueOf(artistId)}); + public static Artist getArtist(Cursor cursor) { Artist artist = new Artist(); if (cursor != null && cursor.moveToFirst()) { final int id = cursor.getInt(0); @@ -71,24 +68,17 @@ public class ArtistLoader { return artist; } - public static List getArtists(Context context, String query) { - Cursor cursor = makeArtistCursor(context, MediaStore.Audio.ArtistColumns.ARTIST + " LIKE ?", new String[]{"%" + query + "%"}); - List artists = new ArrayList<>(); - if (cursor != null && cursor.moveToFirst()) { - do { - final String artistName = cursor.getString(1); - final int id = cursor.getInt(0); - final int albumCount = cursor.getInt(2); - final int songCount = cursor.getInt(3); - - final Artist artist = new Artist(id, artistName, albumCount, songCount); - artists.add(artist); - } while (cursor.moveToNext()); - } - - if (cursor != null) { - cursor.close(); - } - return artists; + public static Cursor makeArtistCursor(final Context context, final String selection, final String[] values) { + return context.getContentResolver().query(MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI, + new String[]{ + /* 0 */ + BaseColumns._ID, + /* 1 */ + MediaStore.Audio.ArtistColumns.ARTIST, + /* 2 */ + MediaStore.Audio.ArtistColumns.NUMBER_OF_ALBUMS, + /* 3 */ + MediaStore.Audio.ArtistColumns.NUMBER_OF_TRACKS + }, selection, values, PreferenceUtils.getInstance(context).getArtistSortOrder()); } } 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 0bc8607c..e3e333a5 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java +++ b/app/src/main/java/com/kabouzeid/gramophone/loader/SongLoader.java @@ -9,7 +9,6 @@ import com.kabouzeid.gramophone.model.Song; import com.kabouzeid.gramophone.util.PreferenceUtils; import java.util.ArrayList; -import java.util.List; /** * @author Karim Abou Zeid (kabouzeid) @@ -18,12 +17,26 @@ public class SongLoader { private static final String BASE_SELECTION = MediaStore.Audio.AudioColumns.IS_MUSIC + "=1" + " AND " + MediaStore.Audio.AudioColumns.TITLE + " != ''"; public static ArrayList getAllSongs(Context context) { - Cursor cursor = makeSongCursor(context); + Cursor cursor = makeSongCursor(context, null, null); + return getSongs(cursor); + } + + public static ArrayList getSongs(final Context context, final String query) { + Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns.TITLE + " LIKE ?", new String[]{"%" + query + "%"}); + return getSongs(cursor); + } + + public static Song getSong(final Context context, final int queryId) { + Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)}); + return getSong(cursor); + } + + public static ArrayList getSongs(final Cursor cursor) { ArrayList songs = new ArrayList<>(); if (cursor != null && cursor.moveToFirst()) { do { - final int id = cursor.getInt(0); final String songName = cursor.getString(1); + final int id = cursor.getInt(0); final String artist = cursor.getString(2); final String album = cursor.getString(3); final long duration = cursor.getLong(4); @@ -35,19 +48,35 @@ public class SongLoader { songs.add(song); } while (cursor.moveToNext()); } + if (cursor != null) cursor.close(); return songs; } - public static Cursor makeSongCursor(final Context context) { - return makeSongCursor(context, MediaStore.Audio.AudioColumns.IS_MUSIC + "=?", new String[]{"1"}); + public static Song getSong(Cursor cursor) { + Song song = new Song(); + if (cursor != null && cursor.moveToFirst()) { + final int id = cursor.getInt(0); + final String songName = cursor.getString(1); + final String artist = cursor.getString(2); + final String album = cursor.getString(3); + final long duration = cursor.getLong(4); + final int trackNumber = cursor.getInt(5); + final int artistId = cursor.getInt(6); + final int albumId = cursor.getInt(7); + song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); + } + if (cursor != null) { + cursor.close(); + } + return song; } public static Cursor makeSongCursor(final Context context, final String selection, final String[] values) { - String finalSelection = BASE_SELECTION; - if (selection != null) { - finalSelection += " AND " + selection; + String baseSelection = BASE_SELECTION; + if (selection != null && !selection.trim().equals("")) { + baseSelection += " AND " + selection; } return context.getContentResolver().query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, @@ -68,50 +97,6 @@ public class SongLoader { MediaStore.Audio.AudioColumns.ARTIST_ID, /* 7 */ MediaStore.Audio.AudioColumns.ALBUM_ID - }, finalSelection, values, PreferenceUtils.getInstance(context).getSongSortOrder()); - } - - public static List getSongs(final Context context, final String query) { - Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns.TITLE + " LIKE ?", new String[]{"%" + query + "%"}); - List songs = new ArrayList<>(); - if (cursor != null && cursor.moveToFirst()) { - do { - final String songName = cursor.getString(1); - final int id = cursor.getInt(0); - final String artist = cursor.getString(2); - final String album = cursor.getString(3); - final long duration = cursor.getLong(4); - final int trackNumber = cursor.getInt(5); - final int artistId = cursor.getInt(6); - final int albumId = cursor.getInt(7); - - final Song song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); - songs.add(song); - } while (cursor.moveToNext()); - } - - if (cursor != null) - cursor.close(); - return songs; - } - - public static Song getSong(final Context context, final int queryId) { - Cursor cursor = makeSongCursor(context, MediaStore.Audio.AudioColumns._ID + "=?", new String[]{String.valueOf(queryId)}); - Song song = null; - if (cursor != null && cursor.moveToFirst()) { - final int id = cursor.getInt(0); - final String songName = cursor.getString(1); - final String artist = cursor.getString(2); - final String album = cursor.getString(3); - final long duration = cursor.getLong(4); - final int trackNumber = cursor.getInt(5); - final int artistId = cursor.getInt(6); - final int albumId = cursor.getInt(7); - song = new Song(id, albumId, artistId, songName, artist, album, duration, trackNumber); - } - if (cursor != null) { - cursor.close(); - } - return song; + }, baseSelection, values, PreferenceUtils.getInstance(context).getSongSortOrder()); } } 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 5a964b58..d8a84498 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java +++ b/app/src/main/java/com/kabouzeid/gramophone/service/MusicService.java @@ -1,7 +1,5 @@ package com.kabouzeid.gramophone.service; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; @@ -12,10 +10,13 @@ import android.content.Intent; import android.content.IntentFilter; import android.graphics.Bitmap; import android.media.AudioManager; +import android.media.MediaMetadataRetriever; import android.media.MediaPlayer; +import android.media.RemoteControlClient; import android.media.audiofx.AudioEffect; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; @@ -24,16 +25,12 @@ import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.preference.PreferenceManager; -import android.support.v4.media.MediaMetadataCompat; -import android.support.v4.media.session.MediaSessionCompat; -import android.support.v4.media.session.PlaybackStateCompat; import android.util.Log; import android.view.View; import android.widget.Toast; import com.kabouzeid.gramophone.R; import com.kabouzeid.gramophone.appwidget.MusicPlayerWidget; -import com.kabouzeid.gramophone.helper.MediaSessionHelper; import com.kabouzeid.gramophone.helper.PlayingNotificationHelper; import com.kabouzeid.gramophone.helper.ShuffleHelper; import com.kabouzeid.gramophone.misc.AppKeys; @@ -85,6 +82,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe public static final int REPEAT_MODE_NONE = 0; public static final int REPEAT_MODE_ALL = 1; public static final int REPEAT_MODE_THIS = 2; + private static final String TAG = MusicService.class.getSimpleName(); private final IBinder musicBind = new MusicBinder(); @@ -100,14 +98,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private boolean thingsRegistered; private boolean saveQueuesAgain; private boolean isSavingQueues; + private PlayingNotificationHelper playingNotificationHelper; private AudioManager audioManager; - private MediaSessionCompat mediaSession; + private RemoteControlClient remoteControlClient; private PowerManager.WakeLock wakeLock; private String currentAlbumArtUri; private MusicPlayerHandler playerHandler; private boolean fadingDown = false; private HandlerThread handlerThread; - private NotificationManager notificationManager; private final BroadcastReceiver becomingNoisyReceiver = new BroadcastReceiver() { @Override @@ -132,6 +130,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe isPlayerPrepared = false; playingQueue = new ArrayList<>(); originalPlayingQueue = new ArrayList<>(); + playingNotificationHelper = new PlayingNotificationHelper(this); shuffleMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_SHUFFLE_MODE, 0); repeatMode = PreferenceManager.getDefaultSharedPreferences(this).getInt(AppKeys.SP_REPEAT_MODE, 0); @@ -145,56 +144,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe handlerThread.start(); playerHandler = new MusicPlayerHandler(this, handlerThread.getLooper()); - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - - setUpMediaSession(); - registerEverything(); } - private void setUpMediaSession() { - mediaSession = new MediaSessionCompat(this, "Phonograph", new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class), getMediaButtonIntent()); - mediaSession.setCallback(new MediaSessionCompat.Callback() { - @Override - public void onPause() { - pausePlaying(false); - } - - @Override - public void onPlay() { - resumePlaying(false); - } - - @Override - public void onSeekTo(long pos) { - //TODO - //seek(pos); - } - - @Override - public void onSkipToNext() { - playNextSong(true); - } - - @Override - public void onSkipToPrevious() { - playPreviousSong(true); - } - - @Override - public void onStop() { - stopPlaying(); - } - }); - mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | - MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); - } - private void registerEverything() { if (!thingsRegistered) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(AudioManager.ACTION_AUDIO_BECOMING_NOISY); registerReceiver(becomingNoisyReceiver, intentFilter); + getAudioManager().registerMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); + initRemoteControlClient(); thingsRegistered = true; } } @@ -206,6 +165,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe return audioManager; } + private void initRemoteControlClient() { + remoteControlClient = new RemoteControlClient(getMediaButtonIntent()); + remoteControlClient.setTransportControlFlags( + RemoteControlClient.FLAG_KEY_MEDIA_PLAY | + RemoteControlClient.FLAG_KEY_MEDIA_PAUSE | + RemoteControlClient.FLAG_KEY_MEDIA_NEXT | + RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); + getAudioManager().registerRemoteControlClient(remoteControlClient); + } + private PendingIntent getMediaButtonIntent() { Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); @@ -259,7 +228,11 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe unregisterEverything(); playerHandler.removeCallbacksAndMessages(null); - handlerThread.quitSafely(); + if (Build.VERSION.SDK_INT >= 18) { + handlerThread.quitSafely(); + } else { + handlerThread.quit(); + } killEverythingAndReleaseResources(); } @@ -279,6 +252,8 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private void unregisterEverything() { if (thingsRegistered) { unregisterReceiver(becomingNoisyReceiver); + getAudioManager().unregisterRemoteControlClient(remoteControlClient); + getAudioManager().unregisterMediaButtonEventReceiver(new ComponentName(getApplicationContext(), MediaButtonIntentReceiver.class)); getAudioManager().abandonAudioFocus(audioFocusListener); thingsRegistered = false; } @@ -286,7 +261,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe private void killEverythingAndReleaseResources() { stopPlaying(); - stopForeground(true); + playingNotificationHelper.killNotification(); savePosition(); saveQueues(); stopSelf(); @@ -300,8 +275,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe player.release(); player = null; } - mediaSession.setActive(false); - mediaSession.release(); notifyChange(PLAYSTATE_CHANGED); } @@ -309,11 +282,11 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe return isPlaying(false); } - private boolean isPlaying(boolean alsoIfIsFadingDown) { - if (!alsoIfIsFadingDown) - return player != null && isPlayerPrepared && player.isPlaying() && !fadingDown; - else + private boolean isPlaying(boolean doNotConsiderFadingDown) { + if (doNotConsiderFadingDown) return player != null && isPlayerPrepared && player.isPlaying(); + else + return player != null && isPlayerPrepared && player.isPlaying() && !fadingDown; } public void saveQueues() { @@ -411,32 +384,14 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe return (getAudioManager().requestAudioFocus(audioFocusListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED); } - private void updateMediaSession(final String what) { + private void updateRemoteControlClient() { final Song song = playingQueue.get(getPosition()); - - int playState = isPlaying() - ? PlaybackStateCompat.STATE_PLAYING - : PlaybackStateCompat.STATE_PAUSED; - - if (what.equals(PLAYSTATE_CHANGED) || what.equals(POSITION_IN_SONG_CHANGED)) { - MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder() - .setActions(getAvailablePlaybackStateActions()) - .setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build()); - } else if (what.equals(META_CHANGED)) { - mediaSession.setMetadata(new MediaMetadataCompat.Builder() - .putString(MediaMetadataCompat.METADATA_KEY_ARTIST, song.artistName) - .putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.albumName) - .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title) - .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, song.duration) - .putLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER, getPosition() + 1) - .putLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS, getPlayingQueue().size()) - .build()); - - MediaSessionHelper.applyState(mediaSession, new PlaybackStateCompat.Builder() - .setActions(getAvailablePlaybackStateActions()) - .setState(playState, player != null ? getSongProgressMillis() : 0, 1.0f).build()); - } - + remoteControlClient + .editMetadata(false) + .putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, song.artistName) + .putString(MediaMetadataRetriever.METADATA_KEY_TITLE, song.title) + .putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, song.duration) + .apply(); currentAlbumArtUri = MusicUtil.getAlbumArtUri(song.albumId).toString(); ImageLoader.getInstance().displayImage(currentAlbumArtUri, new NonViewAware(new ImageSize(-1, -1), ViewScaleType.CROP), new SimpleImageLoadingListener() { @Override @@ -451,7 +406,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe config = Bitmap.Config.ARGB_8888; } albumArt = albumArt.copy(config, false); - updateMediaSessionBitmap(albumArt.copy(albumArt.getConfig(), true)); + updateRemoteControlClientBitmap(albumArt.copy(albumArt.getConfig(), true)); } } } @@ -459,38 +414,16 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { if (currentAlbumArtUri.equals(imageUri)) - updateMediaSessionBitmap(null); + updateRemoteControlClientBitmap(null); } }); } - private void updateMediaSessionBitmap(final Bitmap albumArt) { - MediaMetadataCompat current = mediaSession.getController().getMetadata(); - if (current == null) current = new MediaMetadataCompat.Builder().build(); - - mediaSession.setMetadata(new MediaMetadataCompat.Builder(current) - .putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt) - .build()); - } - - private long getAvailablePlaybackStateActions() { - if (getPlayingQueue() == null || getPlayingQueue().isEmpty()) { - return 0; - } - - long actions = PlaybackStateCompat.ACTION_PLAY_PAUSE; - if (isPlaying()) { - actions |= PlaybackStateCompat.ACTION_PAUSE; - } else { - actions |= PlaybackStateCompat.ACTION_PLAY; - } - if (getPosition() > 0) { - actions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; - } - if (getPosition() < getPlayingQueue().size() - 1) { - actions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; - } - return actions; + private void updateRemoteControlClientBitmap(final Bitmap albumArt) { + remoteControlClient + .editMetadata(false) + .putBitmap(RemoteControlClient.MetadataEditor.BITMAP_KEY_ARTWORK, albumArt) + .apply(); } private void setUpMediaPlayerIfNeeded() { @@ -507,14 +440,7 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } private void updateNotification() { - final boolean isPlaying = isPlaying(); - Notification notification = PlayingNotificationHelper.buildNotification(this, mediaSession.getSessionToken(), getPlayingQueue().get(getPosition()), isPlaying); - if (isPlaying) - startForeground(NOTIFICATION_ID, notification); - else { - notificationManager.notify(NOTIFICATION_ID, notification); - stopForeground(false); - } + playingNotificationHelper.buildNotification(playingQueue.get(position), isPlaying()); } private void updateWidgets() { @@ -742,7 +668,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } public void resumePlaying(boolean forceNoFading) { - mediaSession.setActive(true); if (!forceNoFading && PreferenceUtils.getInstance(this).fadePlayPauseAndInterruptions()) { playerHandler.removeMessages(FADEDOWNANDPAUSE); playerHandler.sendEmptyMessage(FADEUPANDRESUME); @@ -824,7 +749,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe public void seekTo(int millis) { player.seekTo(millis); - notifyChange(POSITION_IN_SONG_CHANGED); } public boolean isPlayerPrepared() { @@ -883,11 +807,6 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe } private void notifyChange(final String what) { - updateMediaSession(what); - if (what.equals(POSITION_IN_SONG_CHANGED)) { - return; - } - final Intent internalIntent = new Intent(what); final int position = getPosition(); if (position >= 0 && !playingQueue.isEmpty()) { @@ -907,11 +826,13 @@ public class MusicService extends Service implements MediaPlayer.OnPreparedListe if (what.equals(PLAYSTATE_CHANGED)) { final boolean isPlaying = isPlaying(); - updateNotification(); + playingNotificationHelper.updatePlayState(isPlaying); MusicPlayerWidget.updateWidgetsPlayState(this, isPlaying); + remoteControlClient.setPlaybackState(isPlaying ? RemoteControlClient.PLAYSTATE_PLAYING : RemoteControlClient.PLAYSTATE_PAUSED); } else if (what.equals(META_CHANGED)) { updateNotification(); updateWidgets(); + updateRemoteControlClient(); } } diff --git a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java index 790fc939..94a25ebd 100644 --- a/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java +++ b/app/src/main/java/com/kabouzeid/gramophone/ui/activities/MainActivity.java @@ -38,6 +38,7 @@ import com.kabouzeid.gramophone.adapter.PagerAdapter; import com.kabouzeid.gramophone.dialogs.AboutDialog; import com.kabouzeid.gramophone.dialogs.CreatePlaylistDialog; import com.kabouzeid.gramophone.helper.MusicPlayerRemote; +import com.kabouzeid.gramophone.helper.SearchQueryHelper; import com.kabouzeid.gramophone.interfaces.CabHolder; import com.kabouzeid.gramophone.interfaces.KabViewsDisableAble; import com.kabouzeid.gramophone.loader.AlbumSongLoader; @@ -405,6 +406,10 @@ public class MainActivity extends AbsFabActivity String mimeType = intent.getType(); boolean handled = false; + if (intent.getAction() != null + && intent.getAction().equals(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH)) { + MusicPlayerRemote.openQueue(SearchQueryHelper.getSongs(this, intent.getExtras()), 0, true); + } if (uri != null && uri.toString().length() > 0) { MusicPlayerRemote.playFile(uri); handled = true; diff --git a/app/src/main/res/layout/notification_controller.xml b/app/src/main/res/layout/notification_controller.xml new file mode 100644 index 00000000..29a5a3af --- /dev/null +++ b/app/src/main/res/layout/notification_controller.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/notification_controller_big.xml b/app/src/main/res/layout/notification_controller_big.xml new file mode 100644 index 00000000..3ae0a71d --- /dev/null +++ b/app/src/main/res/layout/notification_controller_big.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 726ce8dd..9f00a5a4 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -24,6 +24,8 @@ #FFFFFF #99FFFFFF + @color/grey_800 + #29ffffff