From 35a112509e9ed6e7f9e693c8ac3da7ce1da317e0 Mon Sep 17 00:00:00 2001 From: Morgan Lim Date: Sat, 11 Jan 2020 01:55:12 -0500 Subject: [PATCH 1/4] Implement glennguy's MediaStyle notifications daneren2005/Subsonic#914 --- app/build.gradle | 1 + .../nullsum/audinaut/util/Notifications.java | 131 ++++++------------ 2 files changed, 45 insertions(+), 87 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ad90ecf..3de6f51 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,6 +35,7 @@ dependencies { implementation 'com.github.hannesa2:AndroidSlidingUpPanel:4.1.0' implementation 'com.squareup.okhttp3:okhttp:4.2.2' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation "androidx.media:media:1.1.0" } buildscript { diff --git a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java index b8df6d9..fbbbb75 100644 --- a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java +++ b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java @@ -23,14 +23,14 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.os.Build; import android.os.Handler; import android.util.Log; import android.view.KeyEvent; -import android.view.View; -import android.widget.RemoteViews; import androidx.core.app.NotificationCompat; +import androidx.media.app.NotificationCompat.MediaStyle; import net.nullsum.audinaut.R; import net.nullsum.audinaut.activity.SubsonicActivity; @@ -68,27 +68,38 @@ public final class Notifications { final boolean playing = downloadService.getPlayerState() == PlayerState.STARTED; - RemoteViews expandedContentView = new RemoteViews(context.getPackageName(), R.layout.notification_expanded); - setupViews(expandedContentView, context, song, true, playing); - - RemoteViews smallContentView = new RemoteViews(context.getPackageName(), R.layout.notification); - setupViews(smallContentView, context, song, false, playing); - Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class); notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - final Notification notification = new NotificationCompat.Builder(context, CHANNEL_PLAYING_ID) + Intent cancelIntent = new Intent("KEYCODE_MEDIA_STOP") + .setComponent(new ComponentName(context, DownloadService.class)) + .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); + int[] compactActions = new int[]{0, 1, 2}; + MediaStyle mediaStyle = new MediaStyle() + .setShowActionsInCompactView(compactActions) + .setShowCancelButton(true) + .setCancelButtonIntent(PendingIntent.getService(context, 0, cancelIntent, 0)); + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_PLAYING_ID) .setChannelId(CHANNEL_PLAYING_ID) .setSmallIcon(R.drawable.stat_notify_playing) .setContentTitle(song.getTitle()) - .setContentText(song.getTitle()) + .setContentText(song.getArtist()) + .setSubText(song.getAlbum()) + .setTicker(song.getTitle()) .setOngoing(playing) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setCustomContentView(smallContentView) - .setCustomBigContentView(expandedContentView) - .setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, 0)) - .setPriority(NotificationCompat.PRIORITY_LOW).build(); + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setShowWhen(false) + .setLargeIcon(getAlbumArt(context, song)) + .setStyle(mediaStyle) + .setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, 0)); + addActions(context, builder, playing); + final Notification notification = builder.build(); + + if(playing) { + notification.flags |= Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT; + } playShowing = true; if (downloadForeground && downloadShowing) { @@ -116,13 +127,7 @@ public final class Notifications { AudinautWidgetProvider.notifyInstances(context, downloadService, playing); } - private static void setupViews(RemoteViews rv, Context context, MusicDirectory.Entry song, boolean expanded, boolean playing) { - // Use the same text for the ticker and the expanded notification - String title = song.getTitle(); - String arist = song.getArtist(); - String album = song.getAlbum(); - - // Set the album art. + private static Bitmap getAlbumArt(Context context, MusicDirectory.Entry song) { try { ImageLoader imageLoader = SubsonicActivity.getStaticImageLoader(context); Bitmap bitmap = null; @@ -131,90 +136,42 @@ public final class Notifications { } if (bitmap == null) { // set default album art - rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + return BitmapFactory.decodeResource(context.getResources(), R.drawable.unknown_album); } else { - imageLoader.setNowPlayingSmall(bitmap); - rv.setImageViewBitmap(R.id.notification_image, bitmap); + return bitmap; } } catch (Exception x) { Log.w(TAG, "Failed to get notification cover art", x); - rv.setImageViewResource(R.id.notification_image, R.drawable.unknown_album); + return BitmapFactory.decodeResource(context.getResources(), R.drawable.unknown_album); } + } - // set the text for the notifications - rv.setTextViewText(R.id.notification_title, title); - rv.setTextViewText(R.id.notification_artist, arist); - rv.setTextViewText(R.id.notification_album, album); - - boolean persistent = Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_PERSISTENT_NOTIFICATION, false); - if (persistent) { - if (expanded) { - rv.setImageViewResource(R.id.control_pause, playing ? R.drawable.notification_media_pause : R.drawable.notification_media_start); - - rv.setImageViewResource(R.id.control_previous, R.drawable.notification_media_backward); - rv.setImageViewResource(R.id.control_next, R.drawable.notification_media_forward); - } else { - rv.setImageViewResource(R.id.control_previous, playing ? R.drawable.notification_media_pause : R.drawable.notification_media_start); - rv.setImageViewResource(R.id.control_pause, R.drawable.notification_media_forward); - rv.setImageViewResource(R.id.control_next, R.drawable.notification_close); - } - } else { - // Necessary for switching back since it appears to re-use the same layout - rv.setImageViewResource(R.id.control_previous, R.drawable.notification_media_backward); - rv.setImageViewResource(R.id.control_pause, R.drawable.notification_media_pause); - rv.setImageViewResource(R.id.control_next, R.drawable.notification_media_forward); - } - - // Create actions for media buttons + private static void addActions(final Context context, final NotificationCompat.Builder builder, final boolean playing) { PendingIntent pendingIntent; - int previous = 0, pause, next, close = 0; - if (persistent && !expanded) { - pause = R.id.control_previous; - next = R.id.control_pause; - close = R.id.control_next; - } else { - previous = R.id.control_previous; - pause = R.id.control_pause; - next = R.id.control_next; - } - if (persistent && close == 0 && expanded) { - close = R.id.notification_close; - rv.setViewVisibility(close, View.VISIBLE); - } - - if (previous > 0) { - Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); - rv.setOnClickPendingIntent(previous, pendingIntent); - } + Intent prevIntent = new Intent("KEYCODE_MEDIA_PREVIOUS"); + prevIntent.setComponent(new ComponentName(context, DownloadService.class)); + prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PREVIOUS)); + pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); + builder.addAction(R.drawable.notification_media_backward, "Previous", pendingIntent); if (playing) { Intent pauseIntent = new Intent("KEYCODE_MEDIA_PLAY_PAUSE"); pauseIntent.setComponent(new ComponentName(context, DownloadService.class)); pauseIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); pendingIntent = PendingIntent.getService(context, 0, pauseIntent, 0); - rv.setOnClickPendingIntent(pause, pendingIntent); + builder.addAction(R.drawable.notification_media_pause, "Pause", pendingIntent); } else { - Intent prevIntent = new Intent("KEYCODE_MEDIA_START"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); - rv.setOnClickPendingIntent(pause, pendingIntent); + Intent playIntent = new Intent("KEYCODE_MEDIA_PLAY"); + playIntent.setComponent(new ComponentName(context, DownloadService.class)); + playIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY)); + pendingIntent = PendingIntent.getService(context, 0, playIntent, 0); + builder.addAction(R.drawable.notification_media_start, "Play", pendingIntent); } Intent nextIntent = new Intent("KEYCODE_MEDIA_NEXT"); nextIntent.setComponent(new ComponentName(context, DownloadService.class)); nextIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_NEXT)); pendingIntent = PendingIntent.getService(context, 0, nextIntent, 0); - rv.setOnClickPendingIntent(next, pendingIntent); - if (close > 0) { - Intent prevIntent = new Intent("KEYCODE_MEDIA_STOP"); - prevIntent.setComponent(new ComponentName(context, DownloadService.class)); - prevIntent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); - pendingIntent = PendingIntent.getService(context, 0, prevIntent, 0); - rv.setOnClickPendingIntent(close, pendingIntent); - } + builder.addAction(R.drawable.notification_media_forward, "Next", pendingIntent); } public static void hidePlayingNotification(final Context context, final DownloadService downloadService, Handler handler) { From 507c9008aa8f9361029e0d76209cc9ed5567455f Mon Sep 17 00:00:00 2001 From: Morgan Lim Date: Sat, 11 Jan 2020 02:22:27 -0500 Subject: [PATCH 2/4] Colorize MediaStyle notification from album art --- app/build.gradle | 1 + .../main/java/net/nullsum/audinaut/util/Notifications.java | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3de6f51..ccdb3ca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,6 +36,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:4.2.2' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "androidx.media:media:1.1.0" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' } buildscript { diff --git a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java index fbbbb75..e60750e 100644 --- a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java +++ b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java @@ -31,6 +31,7 @@ import android.view.KeyEvent; import androidx.core.app.NotificationCompat; import androidx.media.app.NotificationCompat.MediaStyle; +import android.support.v4.media.session.MediaSessionCompat; import net.nullsum.audinaut.R; import net.nullsum.audinaut.activity.SubsonicActivity; @@ -67,7 +68,6 @@ public final class Notifications { } final boolean playing = downloadService.getPlayerState() == PlayerState.STARTED; - Intent notificationIntent = new Intent(context, SubsonicFragmentActivity.class); notificationIntent.putExtra(Constants.INTENT_EXTRA_NAME_DOWNLOAD, true); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); @@ -76,10 +76,12 @@ public final class Notifications { .setComponent(new ComponentName(context, DownloadService.class)) .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); int[] compactActions = new int[]{0, 1, 2}; + MediaSessionCompat mediaSession = new MediaSessionCompat(context, "Audinaut"); MediaStyle mediaStyle = new MediaStyle() .setShowActionsInCompactView(compactActions) .setShowCancelButton(true) - .setCancelButtonIntent(PendingIntent.getService(context, 0, cancelIntent, 0)); + .setCancelButtonIntent(PendingIntent.getService(context, 0, cancelIntent, 0)) + .setMediaSession(mediaSession.getSessionToken()); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_PLAYING_ID) .setChannelId(CHANNEL_PLAYING_ID) From 153ccdd46aaec20f601a79b8bcc3d447cce47049 Mon Sep 17 00:00:00 2001 From: Morgan Lim Date: Sat, 11 Jan 2020 04:12:02 -0500 Subject: [PATCH 3/4] Manually define duration metadata as -1 to ensure mediastyle notification does not produce unimplemented seek bar on Android 10 --- .../main/java/net/nullsum/audinaut/util/Notifications.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java index e60750e..ed88451 100644 --- a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java +++ b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java @@ -26,12 +26,14 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.os.Handler; +import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import androidx.core.app.NotificationCompat; import androidx.media.app.NotificationCompat.MediaStyle; import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.MediaMetadataCompat; import net.nullsum.audinaut.R; import net.nullsum.audinaut.activity.SubsonicActivity; @@ -77,6 +79,8 @@ public final class Notifications { .putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_STOP)); int[] compactActions = new int[]{0, 1, 2}; MediaSessionCompat mediaSession = new MediaSessionCompat(context, "Audinaut"); + MediaMetadataCompat.Builder metadataBuilder = new MediaMetadataCompat.Builder(); + mediaSession.setMetadata(metadataBuilder.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, -1).build()); MediaStyle mediaStyle = new MediaStyle() .setShowActionsInCompactView(compactActions) .setShowCancelButton(true) From ff4a1f3b13f826e43ed56b96cfa78570db018a07 Mon Sep 17 00:00:00 2001 From: Morgan Lim Date: Sat, 11 Jan 2020 04:13:32 -0500 Subject: [PATCH 4/4] Remove unused import --- app/src/main/java/net/nullsum/audinaut/util/Notifications.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java index ed88451..c0932e5 100644 --- a/app/src/main/java/net/nullsum/audinaut/util/Notifications.java +++ b/app/src/main/java/net/nullsum/audinaut/util/Notifications.java @@ -26,7 +26,6 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; import android.os.Handler; -import android.os.Bundle; import android.util.Log; import android.view.KeyEvent;