2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 13:17:43 +00:00

Fix NPEs and improve handling of nullability

This commit is contained in:
Albert Vaca Cintora 2025-01-28 00:00:34 +01:00
parent d951e3faad
commit c275e26e00
No known key found for this signature in database
2 changed files with 66 additions and 76 deletions

View File

@ -52,7 +52,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
private var spotifyRunning = false private var spotifyRunning = false
// Holds the device and player displayed in the notification // Holds the device and player displayed in the notification
private var notificationDevice: String? = null private var notificationDeviceId: String? = null
private var notificationPlayer: MprisPlayer? = null private var notificationPlayer: MprisPlayer? = null
// Holds the device ids for which we can display a notification // Holds the device ids for which we can display a notification
@ -64,29 +64,27 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// Callback for control via the media session API // Callback for control via the media session API
private val mediaSessionCallback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() { private val mediaSessionCallback: MediaSessionCompat.Callback = object : MediaSessionCompat.Callback() {
override fun onPlay() { override fun onPlay() {
notificationPlayer!!.sendPlay() notificationPlayer?.sendPlay()
} }
override fun onPause() { override fun onPause() {
notificationPlayer!!.sendPause() notificationPlayer?.sendPause()
} }
override fun onSkipToNext() { override fun onSkipToNext() {
notificationPlayer!!.sendNext() notificationPlayer?.sendNext()
} }
override fun onSkipToPrevious() { override fun onSkipToPrevious() {
notificationPlayer!!.sendPrevious() notificationPlayer?.sendPrevious()
} }
override fun onStop() { override fun onStop() {
if (notificationPlayer != null) { notificationPlayer?.sendStop()
notificationPlayer!!.sendStop()
}
} }
override fun onSeekTo(pos: Long) { override fun onSeekTo(pos: Long) {
notificationPlayer!!.sendSetPosition(pos.toInt()) notificationPlayer?.sendSetPosition(pos.toInt())
} }
} }
@ -101,7 +99,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
* @param device The device id * @param device The device id
*/ */
fun onCreate(context: Context?, plugin: MprisPlugin, device: String) { fun onCreate(context: Context?, plugin: MprisPlugin, device: String) {
if (mprisDevices.isEmpty()) { if (mprisDevices.isEmpty) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.registerOnSharedPreferenceChangeListener(this) prefs.registerOnSharedPreferenceChangeListener(this)
} }
@ -139,7 +137,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
plugin.removePlayerListUpdatedHandler("media_notification") plugin.removePlayerListUpdatedHandler("media_notification")
updateMediaNotification() updateMediaNotification()
if (mprisDevices.isEmpty()) { if (mprisDevices.isEmpty) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context) val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.unregisterOnSharedPreferenceChangeListener(this) prefs.unregisterOnSharedPreferenceChangeListener(this)
} }
@ -153,25 +151,27 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
* player and device, while possible. * player and device, while possible.
*/ */
private fun updateCurrentPlayer(): MprisPlayer? { private fun updateCurrentPlayer(): MprisPlayer? {
val player = findPlayer() val player = findPlayer() ?: return null
// Update the last-displayed device and player // Update the last-displayed device and player
notificationDevice = if (player.first == null) null else player.first!!.deviceId notificationDeviceId = if (player.first == null) null else player.first.deviceId
notificationPlayer = player.second notificationPlayer = player.second
return notificationPlayer return notificationPlayer
} }
private fun findPlayer(): Pair<Device?, MprisPlayer?> { private fun findPlayer(): Pair<Device, MprisPlayer>? {
// First try the previously displayed player (if still playing) or the previous displayed device (otherwise) val currentDevice = if (notificationDeviceId != null && mprisDevices.contains(notificationDeviceId)) {
if (notificationDevice != null && mprisDevices.contains(notificationDevice)) { KdeConnect.getInstance().getDevice(notificationDeviceId)
val device = KdeConnect.getInstance().getDevice(notificationDevice)
val player = if (notificationPlayer != null && notificationPlayer!!.isPlaying) {
getPlayerFromDevice(device, notificationPlayer)
} else { } else {
getPlayerFromDevice(device, null) null
} }
// First try the previously displayed player (if still playing) or the previous displayed device (otherwise)
if (currentDevice != null) {
val playingPlayer = notificationPlayer?.takeIf { it.isPlaying }
val player = getPlayerFromDevice(currentDevice, playingPlayer)
if (player != null) { if (player != null) {
return Pair(device, player) return Pair(currentDevice, player)
} }
} }
@ -186,42 +186,33 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// So no player is playing. Try the previously displayed player again // So no player is playing. Try the previously displayed player again
// This will succeed if it's paused: // This will succeed if it's paused:
// that allows pausing and subsequently resuming via the notification // that allows pausing and subsequently resuming via the notification
if (notificationDevice != null && mprisDevices.contains(notificationDevice)) { if (currentDevice != null) {
val device = KdeConnect.getInstance().getDevice(notificationDevice) val player = getPlayerFromDevice(currentDevice, notificationPlayer)
val player = getPlayerFromDevice(device, notificationPlayer)
if (player != null) { if (player != null) {
return Pair(device, player) return Pair(currentDevice, player)
} }
} }
return Pair(null, null)
}
private fun getPlayerFromDevice(device: Device?, preferredPlayer: MprisPlayer?): MprisPlayer? {
if (device == null || !mprisDevices.contains(device.deviceId)) return null
val plugin = device.getPlugin(MprisPlugin::class.java) ?: return null
// First try the preferred player, if supplied
if (plugin.hasPlayer(preferredPlayer) && shouldShowPlayer(preferredPlayer)) {
return preferredPlayer
}
// Otherwise, accept any playing player
val player = plugin.playingPlayer
if (shouldShowPlayer(player)) {
return player
}
return null return null
} }
private fun shouldShowPlayer(player: MprisPlayer?): Boolean { private fun getPlayerFromDevice(device: Device, preferredPlayer: MprisPlayer?): MprisPlayer? {
return player != null && !(player.isSpotify && spotifyRunning) if (!mprisDevices.contains(device.deviceId)) return null
val plugin = device.getPlugin(MprisPlugin::class.java) ?: return null
// First try the preferred player, if supplied & available, otherwise, accept any playing player
val player = preferredPlayer?.takeIf(plugin::hasPlayer)
?: plugin.playingPlayer
?: return null
return player.takeIf(::shouldShowPlayer)
}
private fun shouldShowPlayer(player: MprisPlayer): Boolean {
return !(player.isSpotify && spotifyRunning)
} }
private fun updateRemoteDeviceVolumeControl() { private fun updateRemoteDeviceVolumeControl() {
val plugin = KdeConnect.getInstance().getDevicePlugin(notificationDevice, SystemVolumePlugin::class.java) val plugin = KdeConnect.getInstance().getDevicePlugin(notificationDeviceId, SystemVolumePlugin::class.java)
?: return ?: return
val systemVolumeProvider = fromPlugin(plugin) val systemVolumeProvider = fromPlugin(plugin)
systemVolumeProvider.addStateListener(this) systemVolumeProvider.addStateListener(this)
@ -250,7 +241,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// Make sure our information is up-to-date // Make sure our information is up-to-date
val currentPlayer = updateCurrentPlayer() val currentPlayer = updateCurrentPlayer()
val device = KdeConnect.getInstance().getDevice(notificationDevice) val device = KdeConnect.getInstance().getDevice(notificationDeviceId)
if (device == null) { if (device == null) {
closeMediaNotification() closeMediaNotification()
return return
@ -295,7 +286,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// Create all actions (previous/play/pause/next) // Create all actions (previous/play/pause/next)
val iPlay = Intent(context, MprisMediaNotificationReceiver::class.java).apply { val iPlay = Intent(context, MprisMediaNotificationReceiver::class.java).apply {
setAction(MprisMediaNotificationReceiver.ACTION_PLAY) setAction(MprisMediaNotificationReceiver.ACTION_PLAY)
putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice) putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDeviceId)
putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName) putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName)
} }
val piPlay = PendingIntent.getBroadcast( val piPlay = PendingIntent.getBroadcast(
@ -310,7 +301,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
val iPause = Intent(context, MprisMediaNotificationReceiver::class.java).apply { val iPause = Intent(context, MprisMediaNotificationReceiver::class.java).apply {
setAction(MprisMediaNotificationReceiver.ACTION_PAUSE) setAction(MprisMediaNotificationReceiver.ACTION_PAUSE)
putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice) putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDeviceId)
putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName) putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName)
} }
val piPause = PendingIntent.getBroadcast( val piPause = PendingIntent.getBroadcast(
@ -325,7 +316,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
val iPrevious = Intent(context, MprisMediaNotificationReceiver::class.java).apply { val iPrevious = Intent(context, MprisMediaNotificationReceiver::class.java).apply {
setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS) setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS)
putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice) putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDeviceId)
putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName) putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName)
} }
val piPrevious = PendingIntent.getBroadcast( val piPrevious = PendingIntent.getBroadcast(
@ -340,7 +331,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
val iNext = Intent(context, MprisMediaNotificationReceiver::class.java).apply { val iNext = Intent(context, MprisMediaNotificationReceiver::class.java).apply {
setAction(MprisMediaNotificationReceiver.ACTION_NEXT) setAction(MprisMediaNotificationReceiver.ACTION_NEXT)
putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice) putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDeviceId)
putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName) putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName)
} }
val piNext = PendingIntent.getBroadcast( val piNext = PendingIntent.getBroadcast(
@ -354,7 +345,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
) )
val iOpenActivity = Intent(context, MprisActivity::class.java).apply { val iOpenActivity = Intent(context, MprisActivity::class.java).apply {
putExtra("deviceId", notificationDevice) putExtra("deviceId", notificationDeviceId)
putExtra("player", currentPlayer.playerName) putExtra("player", currentPlayer.playerName)
} }
@ -392,7 +383,7 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
if (!currentPlayer.isPlaying) { if (!currentPlayer.isPlaying) {
val iCloseNotification = Intent(context, MprisMediaNotificationReceiver::class.java) val iCloseNotification = Intent(context, MprisMediaNotificationReceiver::class.java)
iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION) iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION)
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice) iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDeviceId)
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName) iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, currentPlayer.playerName)
val piCloseNotification = PendingIntent.getBroadcast( val piCloseNotification = PendingIntent.getBroadcast(
context, context,
@ -450,17 +441,18 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// Display the notification // Display the notification
synchronized(instance) { synchronized(instance) {
if (mediaSession == null) { val mediaSession = mediaSession ?: MediaSessionCompat(context!!, MPRIS_MEDIA_SESSION_TAG).apply {
mediaSession = MediaSessionCompat(context!!, MPRIS_MEDIA_SESSION_TAG) setCallback(mediaSessionCallback, Handler(context!!.mainLooper))
mediaSession!!.setCallback(mediaSessionCallback, Handler(context!!.mainLooper))
} }
mediaSession!!.setMetadata(metadata.build()) mediaSession.setMetadata(metadata.build())
mediaSession!!.setPlaybackState(playbackState.build()) mediaSession.setPlaybackState(playbackState.build())
mediaStyle.setMediaSession(mediaSession!!.sessionToken) mediaStyle.setMediaSession(mediaSession.sessionToken)
notification.setStyle(mediaStyle) notification.setStyle(mediaStyle)
mediaSession!!.isActive = true mediaSession.isActive = true
val nm = ContextCompat.getSystemService(context!!, NotificationManager::class.java) ContextCompat.getSystemService(context!!, NotificationManager::class.java)?.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build())
nm!!.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build()) if (this.mediaSession == null) {
this.mediaSession = mediaSession
}
} }
} }
@ -472,16 +464,14 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
// Clear the current player and media session // Clear the current player and media session
notificationPlayer = null notificationPlayer = null
synchronized(instance) { synchronized(instance) {
if (mediaSession != null) { mediaSession?.apply {
mediaSession!!.setPlaybackState(PlaybackStateCompat.Builder().build()) setPlaybackState(PlaybackStateCompat.Builder().build())
mediaSession!!.setMetadata(MediaMetadataCompat.Builder().build()) setMetadata(MediaMetadataCompat.Builder().build())
mediaSession!!.isActive = false isActive = false
mediaSession!!.release() release()
mediaSession = null
val currentProvider = currentProvider
currentProvider?.release()
} }
mediaSession = null
currentProvider?.release()
} }
} }
@ -529,8 +519,8 @@ class MprisMediaSession : OnSharedPreferenceChangeListener, NotificationReceiver
} }
} }
private fun StatusBarNotification?.isSpotify(): Boolean = private fun StatusBarNotification.isSpotify(): Boolean =
this?.packageName == SPOTIFY_PACKAGE_NAME this.packageName == SPOTIFY_PACKAGE_NAME
companion object { companion object {
const val TAG = "MprisMediaSession" const val TAG = "MprisMediaSession"

View File

@ -413,7 +413,7 @@ class MprisPlugin : Plugin() {
*/ */
get() = players.values.stream().filter(MprisPlayer::isPlaying).findFirst().orElse(null) get() = players.values.stream().filter(MprisPlayer::isPlaying).findFirst().orElse(null)
fun hasPlayer(player: MprisPlayer?): Boolean = player != null && players.containsValue(player) fun hasPlayer(player: MprisPlayer): Boolean = players.containsValue(player)
private fun requestPlayerList() { private fun requestPlayerList() {
val np = NetworkPacket(PACKET_TYPE_MPRIS_REQUEST).apply { val np = NetworkPacket(PACKET_TYPE_MPRIS_REQUEST).apply {