From 1072a3bf855f1079d721405e2b21fd552b905c64 Mon Sep 17 00:00:00 2001 From: Karim Abou Zeid Date: Wed, 17 Jun 2015 01:16:58 +0200 Subject: [PATCH] Reverted "MediaSession update" back to RemoteControlClient. Added back the "x" to close the notification. Notification now also colored below Lollipop. Its now possible to play music from Google Now voice search. --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 5 + .../gramophone/helper/MusicPlayerRemote.java | 4 + .../helper/PlayingNotificationHelper.java | 238 +++++++++++++----- .../gramophone/helper/SearchQueryHelper.java | 90 +++++++ .../gramophone/loader/AlbumLoader.java | 79 +++--- .../gramophone/loader/ArtistLoader.java | 74 +++--- .../gramophone/loader/SongLoader.java | 91 +++---- .../gramophone/service/MusicService.java | 177 ++++--------- .../ui/activities/MainActivity.java | 5 + .../res/layout/notification_controller.xml | 129 ++++++++++ .../layout/notification_controller_big.xml | 153 +++++++++++ app/src/main/res/values/colors.xml | 2 + 13 files changed, 717 insertions(+), 334 deletions(-) create mode 100644 app/src/main/java/com/kabouzeid/gramophone/helper/SearchQueryHelper.java create mode 100644 app/src/main/res/layout/notification_controller.xml create mode 100644 app/src/main/res/layout/notification_controller_big.xml 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