From d07b5e793fc80c0414ec992fb07c950b14edbd29 Mon Sep 17 00:00:00 2001 From: IllusionMan1212 Date: Fri, 25 Feb 2022 02:19:01 +0200 Subject: [PATCH] feat: get duration, playback state and album art from notification --- .../lyricsgrabbr/NotificationListener.kt | 37 +++++++++++--- .../lyricsgrabbr/NotificationStreamHandler.kt | 6 +++ lib/main.dart | 22 ++++---- lib/notification_listener.dart | 50 +++++++++++++------ 4 files changed, 80 insertions(+), 35 deletions(-) diff --git a/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationListener.kt b/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationListener.kt index 19e8d08..dee2d7f 100644 --- a/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationListener.kt +++ b/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationListener.kt @@ -1,43 +1,58 @@ package com.illusionware.lyricsgrabbr.lyricsgrabbr import android.app.Notification +import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.media.MediaMetadata +import android.media.session.MediaController +import android.media.session.MediaSession import android.service.notification.NotificationListenerService import android.service.notification.StatusBarNotification import org.json.JSONException import org.json.JSONObject import android.os.Bundle +import android.util.Base64 +import java.io.ByteArrayOutputStream /** * Notification listening service. Intercepts notifications if permission is given to do so. */ class NotificationListener : NotificationListenerService() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - getCurrentNotifications() + getCurrentNotifications(applicationContext) return super.onStartCommand(intent, flags, startId) } override fun onListenerConnected() { - getCurrentNotifications() + getCurrentNotifications(applicationContext) } override fun onNotificationPosted(sbn: StatusBarNotification) { - sendNotificationBroadcast(sbn) + sendNotificationBroadcast(sbn, applicationContext) } - private fun getCurrentNotifications() { + private fun getCurrentNotifications(context: Context) { val sbns = activeNotifications for (sbn in sbns) { // TODO: remove this check and figure out a way to send a fucking list instead if (sbn.packageName == "com.spotify.music") { - sendNotificationBroadcast(sbn) + sendNotificationBroadcast(sbn, context) } } } - private fun sendNotificationBroadcast(sbn: StatusBarNotification) { - // TODO: get specific media player notification data - // such as onPlay and onNextSong or whatever tf + private fun sendNotificationBroadcast(sbn: StatusBarNotification, context: Context) { + // get the media session stuff + val mediaSessionToken = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION) + val mediaCtrl = mediaSessionToken?.let { MediaController(context, it) } + val duration = mediaCtrl?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION)?.div(1000) + val playbackState = mediaCtrl?.playbackState?.state + + val albumArt = mediaCtrl?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART) + val outputStream = ByteArrayOutputStream() + albumArt?.compress(Bitmap.CompressFormat.PNG, 75, outputStream) + val artB64 = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP) // Retrieve package name to set as title. val packageName = sbn.packageName @@ -52,6 +67,9 @@ class NotificationListener : NotificationListenerService() { intent.putExtra(NOTIFICATION_PACKAGE_MESSAGE, packageMessage) intent.putExtra(NOTIFICATION_PACKAGE_TEXT, packageText) intent.putExtra(NOTIFICATION_PACKAGE_EXTRA, packageExtra) + intent.putExtra(NOTIFICATION_PACKAGE_ART, artB64) + intent.putExtra(NOTIFICATION_PACKAGE_DURATION, duration) + intent.putExtra(NOTIFICATION_PACKAGE_PLAYBACK_STATE, playbackState) sendBroadcast(intent) } @@ -61,6 +79,9 @@ class NotificationListener : NotificationListenerService() { const val NOTIFICATION_PACKAGE_MESSAGE = "package_message" const val NOTIFICATION_PACKAGE_TEXT = "package_text" const val NOTIFICATION_PACKAGE_EXTRA = "package_extra" + const val NOTIFICATION_PACKAGE_ART = "package_art" + const val NOTIFICATION_PACKAGE_PLAYBACK_STATE = "package_playback_state" + const val NOTIFICATION_PACKAGE_DURATION = "package_duration" } private fun convertBundleToJsonString(extra: Bundle): String { diff --git a/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationStreamHandler.kt b/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationStreamHandler.kt index bf66b92..3d1b6b9 100644 --- a/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationStreamHandler.kt +++ b/android/app/src/main/kotlin/com/illusionware/lyricsgrabbr/lyricsgrabbr/NotificationStreamHandler.kt @@ -35,11 +35,17 @@ class NotificationStreamHandler(private val context: Context) : StreamHandler { val packageMessage = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_MESSAGE) val packageText = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_TEXT) val packageExtra = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_EXTRA) + val packageArt = intent.getStringExtra(NotificationListener.NOTIFICATION_PACKAGE_ART) + val packagePlaybackState = intent.getIntExtra(NotificationListener.NOTIFICATION_PACKAGE_PLAYBACK_STATE, -1) + val packageDuration = intent.getLongExtra(NotificationListener.NOTIFICATION_PACKAGE_DURATION, -1) val map = HashMap() map["packageName"] = packageName!! map["packageMessage"] = packageMessage!! map["packageText"] = packageText!! map["packageExtra"] = packageExtra!! + map["packageArt"] = packageArt!! + map["packagePlaybackState"] = packagePlaybackState + map["packageDuration"] = packageDuration eventSink?.success(map) } } diff --git a/lib/main.dart b/lib/main.dart index 5807f68..031c01f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,8 +13,7 @@ const notificationId = 0; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - // TODO: do the preconfiguration for the android notifications. - // e.g: notification icon + // TODO: change the default flutter icon to my own. const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); const InitializationSettings initializationSettings = @@ -143,17 +142,15 @@ class _HomePageState extends State with WidgetsBindingObserver { // TODO: if the app is in the background, only update our notif // if the app is in the foreground, update the notif, and // fetch the new lyrics and display them. - var song = event.packageText; - var artist = event.packageMessage; - sendNotification(song, artist); + sendNotification(event); setState(() { _notifs.add(event); }); } } - Future sendNotification(String title, String body) async { - const AndroidNotificationDetails androidNotification = + Future sendNotification(NotificationEvent notification) async { + final AndroidNotificationDetails androidNotification = AndroidNotificationDetails( "Lyrics", "Lyrics", @@ -162,15 +159,16 @@ class _HomePageState extends State with WidgetsBindingObserver { showWhen: false, ongoing: true, playSound: false, + largeIcon: ByteArrayAndroidBitmap.fromBase64String(notification.art), ); - const NotificationDetails notificationDetails = + final NotificationDetails notificationDetails = NotificationDetails(android: androidNotification); await flutterLocalNotificationsPlugin.show( notificationId, - title, - body, + '${notification.songTitle} - ${notification.artist}', + 'Tap to get lyrics', notificationDetails, ); } @@ -204,8 +202,8 @@ class _HomePageState extends State with WidgetsBindingObserver { itemBuilder: (BuildContext context, int idx) { final entry = _notifs[idx]; return ListTile( - leading: Text(entry.packageText), - trailing: Text(entry.packageMessage), + leading: Text(entry.songTitle), + trailing: Text(entry.artist), ); }), floatingActionButton: FloatingActionButton( diff --git a/lib/notification_listener.dart b/lib/notification_listener.dart index e49f1d6..cb1f2cb 100644 --- a/lib/notification_listener.dart +++ b/lib/notification_listener.dart @@ -13,19 +13,32 @@ class NotificationException implements Exception { } } -class NotificationEvent { - String packageMessage; - String packageName; - String packageExtra; - String packageText; - DateTime timeStamp; +abstract class PlaybackState { + static const STATE_INVALID = -1; + static const STATE_PAUSED = 2; + static const STATE_PLAYING = 3; +} - NotificationEvent({ +class NotificationEvent { + final String artist; + final String packageName; + final String packageExtra; + final String songTitle; + final String art; + final int playbackState; + final int duration; + + final DateTime timeStamp; + + const NotificationEvent({ required this.packageName, - required this.packageMessage, + required this.artist, required this.timeStamp, required this.packageExtra, - required this.packageText + required this.songTitle, + required this.art, + required this.playbackState, + required this.duration }); factory NotificationEvent.fromMap(Map map) { @@ -34,13 +47,20 @@ class NotificationEvent { String message = map['packageMessage']; String text = map['packageText']; String extra = map['packageExtra']; + String icon = map['packageArt']; + int playbackState = map['packagePlaybackState']; + int duration = map['packageDuration']; - return NotificationEvent(packageName: name, packageMessage: message, timeStamp: time,packageText: text , packageExtra: extra); - } - - @override - String toString() { - return "Notification Event \n Package Name: $packageName \n - Timestamp: $timeStamp \n - Package Message: $packageMessage"; + return NotificationEvent( + packageName: name, + artist: message, + timeStamp: time, + songTitle: text, + packageExtra: extra, + art: icon, + playbackState: playbackState, + duration: duration + ); } }