From 2f127b30478804d9821295f11dc236306d51adb4 Mon Sep 17 00:00:00 2001 From: Matthijs Tijink Date: Mon, 11 Dec 2017 12:01:13 +0100 Subject: [PATCH] Add MPRIS media control notification Summary: BUG: 337485 Adds a notification to show and control mpris players. It shows the title, artist etc. (so depends on D9083, but can easily be adapted to work without it, but that leads to less features). The notification appears as soon as one of your connected devices plays music. If multiple devices/players are playing, it shows the information and controls for only one of these. If it stops playing, it tries to switch to another playing device/player. If those do not exist, it shows the same player, but allows starting it again. Dismissing the notification is only possible if the showed player is paused (as effect, only when all players are paused). It automatically closes if the device or player disappears or disconnects and no other players are playing. I think this behaviour is intuitive, other native android music players have similar behaviour. About the implementation: there are two parts to this: the notification and the media session control API. The first shows the notification and its controls. The second allows lock screen controls on older Android versions and control using e.g. headphone buttons. Since nearly all code is shared between the two parts, and the rest is mostly straightforward, I put them in the same diff. Test Plan: Tested on Android Nougat 7.1 (shows the notification with buttons, as expected; no lock screen controls, as expected) and Android Gingerbread 2.3 (shows a notification without buttons, lock screen controls work, so as expected). I am not able to test with multiple desktops, so testing that would be appreciated. Disabling buttons when not available should work, but all players I tested always allowed next/previous/play/pause. Reviewers: #kde_connect, nicolasfella Reviewed By: #kde_connect, nicolasfella Subscribers: apol, albertvaka, nicolasfella Tags: #kde_connect Maniphest Tasks: T6512 Differential Revision: https://phabricator.kde.org/D9266 --- AndroidManifest.xml | 7 + res/drawable/ic_next_white.xml | 9 + res/drawable/ic_pause_white.xml | 9 + res/drawable/ic_play_white.xml | 9 + res/drawable/ic_previous_white.xml | 9 + res/values/strings.xml | 4 + res/xml/mprisplugin_preferences.xml | 9 +- .../MprisMediaNotificationReceiver.java | 86 ++++ .../MprisPlugin/MprisMediaSession.java | 404 ++++++++++++++++++ .../Plugins/MprisPlugin/MprisPlugin.java | 15 + 10 files changed, 560 insertions(+), 1 deletion(-) create mode 100644 res/drawable/ic_next_white.xml create mode 100644 res/drawable/ic_pause_white.xml create mode 100644 res/drawable/ic_play_white.xml create mode 100644 res/drawable/ic_previous_white.xml create mode 100644 src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java create mode 100644 src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4ac9b207..8e0d0a3d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -154,6 +154,13 @@ android:name="android.support.PARENT_ACTIVITY" android:value="org.kde.kdeconnect.UserInterface.MaterialActivity" /> + + + + + + + + diff --git a/res/drawable/ic_pause_white.xml b/res/drawable/ic_pause_white.xml new file mode 100644 index 00000000..8356ff57 --- /dev/null +++ b/res/drawable/ic_pause_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_play_white.xml b/res/drawable/ic_play_white.xml new file mode 100644 index 00000000..81a8f74f --- /dev/null +++ b/res/drawable/ic_play_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/drawable/ic_previous_white.xml b/res/drawable/ic_previous_white.xml new file mode 100644 index 00000000..75287857 --- /dev/null +++ b/res/drawable/ic_previous_white.xml @@ -0,0 +1,9 @@ + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index a8094124..2b10f911 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -127,6 +127,7 @@ Remote control KDE Connect Settings Play + Pause Previous Rewind Fast-forward @@ -151,6 +152,9 @@ 60000000 120000000 + Show media control notification + Allows controlling your media players without opening KDE Connect. + mpris_notification_enabled Share To... This device uses an old protocol version This device uses a newer protocol version diff --git a/res/xml/mprisplugin_preferences.xml b/res/xml/mprisplugin_preferences.xml index c565a357..bc696ae4 100644 --- a/res/xml/mprisplugin_preferences.xml +++ b/res/xml/mprisplugin_preferences.xml @@ -12,4 +12,11 @@ android:entryValues="@array/mpris_time_entries_values" android:defaultValue="@string/mpris_time_default" /> - \ No newline at end of file + + + diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java new file mode 100644 index 00000000..e3d5afc5 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaNotificationReceiver.java @@ -0,0 +1,86 @@ +/* + * Copyright 2017 Matthijs Tijink + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package org.kde.kdeconnect.Plugins.MprisPlugin; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.support.v4.media.session.MediaSessionCompat; +import android.view.KeyEvent; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; + +/** + * Called when the mpris media notification's buttons are pressed + */ +public class MprisMediaNotificationReceiver extends BroadcastReceiver { + public static final String ACTION_PLAY = "ACTION_PLAY"; + public static final String ACTION_PAUSE = "ACTION_PAUSE"; + public static final String ACTION_PREVIOUS = "ACTION_PREVIOUS"; + public static final String ACTION_NEXT = "ACTION_NEXT"; + public static final String ACTION_CLOSE_NOTIFICATION = "ACTION_CLOSE_NOTIFICATION"; + + public static final String EXTRA_DEVICE_ID = "deviceId"; + public static final String EXTRA_MPRIS_PLAYER = "player"; + + @Override + public void onReceive(Context context, Intent intent) { + //First case: buttons send by other applications via the media session APIs + if (Intent.ACTION_MEDIA_BUTTON.equals(intent.getAction())) { + //Route these buttons to the media session, which will handle them + MediaSessionCompat mediaSession = MprisMediaSession.getMediaSession(); + if (mediaSession == null) return; + mediaSession.getController().dispatchMediaButtonEvent((KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT)); + } else { + //Second case: buttons on the notification, which we created ourselves + + //Get the correct device, the mpris plugin and the mpris player + BackgroundService service = BackgroundService.getInstance(); + if (service == null) return; + Device device = service.getDevice(intent.getStringExtra(EXTRA_DEVICE_ID)); + if (device == null) return; + MprisPlugin mpris = device.getPlugin(MprisPlugin.class); + if (mpris == null) return; + MprisPlugin.MprisPlayer player = mpris.getPlayerStatus(intent.getStringExtra(EXTRA_MPRIS_PLAYER)); + if (player == null) return; + + //Forward the action to the player + switch (intent.getAction()) { + case ACTION_PLAY: + player.play(); + break; + case ACTION_PAUSE: + player.pause(); + break; + case ACTION_PREVIOUS: + player.previous(); + break; + case ACTION_NEXT: + player.next(); + break; + case ACTION_CLOSE_NOTIFICATION: + //The user dismissed the notification: actually handle its removal correctly + MprisMediaSession.getInstance().closeMediaNotification(); + } + } + } +} diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java new file mode 100644 index 00000000..d6ef3a25 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisMediaSession.java @@ -0,0 +1,404 @@ +/* + * Copyright 2017 Matthijs Tijink + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +package org.kde.kdeconnect.Plugins.MprisPlugin; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.BitmapFactory; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.preference.PreferenceManager; +import android.support.v4.content.SharedPreferencesCompat; +import android.support.v4.media.MediaMetadataCompat; +import android.support.v4.media.session.MediaSessionCompat; +import android.support.v4.media.session.PlaybackStateCompat; +import android.support.v7.app.NotificationCompat; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect_tp.R; + +import java.util.HashSet; + +/** + * Controls the mpris media control notification + * + * There are two parts to this: + * - The notification (with buttons etc.) + * - The media session (via MediaSessionCompat; for lock screen control on + * older Android version. And in the future for lock screen album covers) + */ +public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceChangeListener { + public final static int MPRIS_MEDIA_NOTIFICATION_ID = 0x91b70463; // echo MprisNotification | md5sum | head -c 8 + public final static String MPRIS_MEDIA_SESSION_TAG = "org.kde.kdeconnect_tp.media_session"; + + private static MprisMediaSession instance = new MprisMediaSession(); + public static MprisMediaSession getInstance() { + return instance; + } + public static MediaSessionCompat getMediaSession() { + return instance.mediaSession; + } + + //Holds the device and player displayed in the notification + private String notificationDevice = null; + private MprisPlugin.MprisPlayer notificationPlayer = null; + //Holds the device ids for which we can display a notification + private HashSet mprisDevices = new HashSet<>(); + + private Context context; + private MediaSessionCompat mediaSession; + + //Callback for mpris plugin updates + private Handler mediaNotificationHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + updateMediaNotification(); + } + }; + //Callback for control via the media session API + private MediaSessionCompat.Callback mediaSessionCallback = new MediaSessionCompat.Callback() { + @Override + public void onPlay() { + notificationPlayer.play(); + } + + @Override + public void onPause() { + notificationPlayer.pause(); + } + + @Override + public void onSkipToNext() { + notificationPlayer.next(); + } + + @Override + public void onSkipToPrevious() { + notificationPlayer.previous(); + } + + @Override + public void onStop() { + notificationPlayer.stop(); + } + }; + + /** + * Called by the mpris plugin when it wants media control notifications for its device + * + * Can be called multiple times, once for each device + * @param _context The context + * @param mpris The mpris plugin + * @param device The device id + */ + public void onCreate(Context _context, MprisPlugin mpris, String device) { + if (mprisDevices.isEmpty()) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(_context); + prefs.registerOnSharedPreferenceChangeListener(this); + } + context = _context; + mprisDevices.add(device); + + mpris.setPlayerListUpdatedHandler("media_notification", mediaNotificationHandler); + mpris.setPlayerStatusUpdatedHandler("media_notification", mediaNotificationHandler); + + updateMediaNotification(); + } + + /** + * Called when a device disconnects/does not want notifications anymore + * + * Can be called multiple times, once for each device + * @param mpris The mpris plugin + * @param device The device id + */ + public void onDestroy(MprisPlugin mpris, String device) { + mprisDevices.remove(device); + mpris.removePlayerStatusUpdatedHandler("media_notification"); + mpris.removePlayerListUpdatedHandler("media_notification"); + updateMediaNotification(); + + if (mprisDevices.isEmpty()) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + prefs.unregisterOnSharedPreferenceChangeListener(this); + } + } + + /** + * Updates which device+player we're going to use in the notification + * + * Prefers playing devices/mpris players, but tries to keep displaying the same + * player and device, while possible. + * @param service The background service + */ + private void updateCurrentPlayer(BackgroundService service) { + Device device = null; + MprisPlugin.MprisPlayer playing = null; + + //First try the previously displayed player + if (notificationDevice != null && mprisDevices.contains(notificationDevice) && notificationPlayer != null) { + device = service.getDevice(notificationDevice); + } + MprisPlugin mpris = null; + if (device != null) { + mpris = device.getPlugin(MprisPlugin.class); + } + if (mpris != null) { + playing = mpris.getPlayerStatus(notificationPlayer.getPlayer()); + } + + //If nonexistant or not playing, try a different player for the same device + if ((playing == null || !playing.isPlaying()) && mpris != null) { + MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer(); + + //Only replace the previously found player if we really found one + if (playingPlayer != null) { + playing = playingPlayer; + } + } + + //If nonexistant or not playing, try a different player for another device + if (playing == null || !playing.isPlaying()) { + for (Device otherDevice : service.getDevices().values()) { + //First, check if we actually display notification for this device + if (!mprisDevices.contains(otherDevice.getDeviceId())) continue; + mpris = otherDevice.getPlugin(MprisPlugin.class); + if (mpris == null) continue; + + MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer(); + //Only replace the previously found player if we really found one + if (playingPlayer != null) { + playing = playingPlayer; + device = otherDevice; + break; + } + } + } + + //Update the last-displayed device and player + notificationDevice = device == null ? null : device.getDeviceId(); + notificationPlayer = playing; + } + + /** + * Update the media control notification + */ + private void updateMediaNotification() { + BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + //If the user disabled the media notification, do not show it + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) { + closeMediaNotification(); + return; + } + + //Make sure our information is up-to-date + updateCurrentPlayer(service); + + //If the player disappeared (and no other playing one found), just remove the notification + if (notificationPlayer == null) { + closeMediaNotification(); + return; + } + + //Update the metadata and playback status + if (mediaSession == null) { + mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG); + mediaSession.setCallback(mediaSessionCallback); + mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); + } + MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder(); + + //Fallback because older KDE connect versions do not support getTitle() + if (!notificationPlayer.getTitle().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle()); + } else { + metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong()); + } + if (!notificationPlayer.getArtist().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist()); + } + if (!notificationPlayer.getAlbum().isEmpty()) { + metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum()); + } + if (notificationPlayer.getLength() > 0) { + metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength()); + } + + mediaSession.setMetadata(metadata.build()); + PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder(); + + if (notificationPlayer.isPlaying()) { + playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f); + } else { + playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f); + } + + //Create all actions (previous/play/pause/next) + Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class); + iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY); + iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); + PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder( + R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay); + + Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class); + iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE); + iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); + PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder( + R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause); + + Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class); + iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS); + iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); + PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder( + R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious); + + Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class); + iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT); + iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); + PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT); + NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder( + R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext); + + Intent iOpenActivity = new Intent(service, MprisActivity.class); + iOpenActivity.putExtra("deviceId", notificationDevice); + iOpenActivity.putExtra("player", notificationPlayer.getPlayer()); + PendingIntent piOpenActivity = PendingIntent.getActivity(service, 0, iOpenActivity, PendingIntent.FLAG_UPDATE_CURRENT); + + //Create the notification + final NotificationCompat.Builder notification = new NotificationCompat.Builder(service); + notification + .setAutoCancel(false) + .setContentIntent(piOpenActivity) + .setSmallIcon(R.drawable.ic_play_white) + .setShowWhen(false) + .setColor(service.getResources().getColor(R.color.primary)); + + if (!notificationPlayer.getTitle().isEmpty()) { + notification.setContentTitle(notificationPlayer.getTitle()); + } else { + notification.setContentTitle(notificationPlayer.getCurrentSong()); + } + //Only set the notification body text if we have an author and/or album + if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) { + notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")"); + } else if (!notificationPlayer.getArtist().isEmpty()) { + notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")"); + } else if (!notificationPlayer.getAlbum().isEmpty()) { + notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")"); + } else { + notification.setContentText(notificationPlayer.getPlayer()); + } + + if (!notificationPlayer.isPlaying()) { + Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class); + iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION); + iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice); + iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer()); + PendingIntent piCloseNotification = PendingIntent.getActivity(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT); + notification.setDeleteIntent(piCloseNotification); + } + + //Add media control actions + int numActions = 0; + long playbackActions = 0; + if (notificationPlayer.isGoPreviousAllowed()) { + notification.addAction(aPrevious.build()); + playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS; + ++numActions; + } + if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) { + notification.addAction(aPause.build()); + playbackActions |= PlaybackStateCompat.ACTION_PAUSE; + ++numActions; + } + if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) { + notification.addAction(aPlay.build()); + playbackActions |= PlaybackStateCompat.ACTION_PLAY; + ++numActions; + } + if (notificationPlayer.isGoNextAllowed()) { + notification.addAction(aNext.build()); + playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT; + ++numActions; + } + playbackState.setActions(playbackActions); + mediaSession.setPlaybackState(playbackState.build()); + + //Only allow deletion if no music is notificationPlayer + if (notificationPlayer.isPlaying()) { + notification.setOngoing(true); + } else { + notification.setOngoing(false); + } + + //Use the MediaStyle notification, so it feels like other media players. That also allows adding actions + NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle(); + if (numActions == 1) { + mediaStyle.setShowActionsInCompactView(0); + } else if (numActions == 2) { + mediaStyle.setShowActionsInCompactView(0, 1); + } else if (numActions >= 3) { + mediaStyle.setShowActionsInCompactView(0, 1, 2); + } + mediaStyle.setMediaSession(mediaSession.getSessionToken()); + notification.setStyle(mediaStyle); + + //Display the notification + mediaSession.setActive(true); + final NotificationManager nm = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build()); + } + }); + } + + public void closeMediaNotification() { + //Remove the notification + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel(MPRIS_MEDIA_NOTIFICATION_ID); + + //Clear the current player and media session + notificationPlayer = null; + if (mediaSession != null) { + mediaSession.release(); + mediaSession = null; + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + updateMediaNotification(); + } +} diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java index cabc1637..c7a4bf12 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java @@ -229,6 +229,7 @@ public class MprisPlugin extends Plugin { @Override public boolean onCreate() { requestPlayerList(); + MprisMediaSession.getInstance().onCreate(context.getApplicationContext(), this, device.getDeviceId()); //Always request the player list so the data is up-to-date requestPlayerList(); @@ -243,6 +244,7 @@ public class MprisPlugin extends Plugin { public void onDestroy() { players.clear(); AlbumArtCache.deregisterPlugin(this); + MprisMediaSession.getInstance().onDestroy(this, device.getDeviceId()); } private void sendCommand(String player, String method, String value) { @@ -391,6 +393,19 @@ public class MprisPlugin extends Plugin { return new MprisPlayer(); } + /** + * Returns a playing mpris player, if any exist + * @return null if no players are playing, a playing player otherwise + */ + public MprisPlayer getPlayingPlayer() { + for (MprisPlayer player : players.values()) { + if (player.isPlaying()) { + return player; + } + } + return null; + } + private void requestPlayerList() { NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST); np.set("requestPlayerList",true);