Make PlaybackService perpetually foreground
Make PlaybackService never leave the foreground unless forced to, will add an option to close the service later.
This commit is contained in:
parent
afa50ad531
commit
09971afb42
6 changed files with 37 additions and 13 deletions
|
@ -27,6 +27,9 @@
|
||||||
<service
|
<service
|
||||||
android:name=".playback.PlaybackService"
|
android:name=".playback.PlaybackService"
|
||||||
android:icon="@drawable/ic_launcher_foreground"
|
android:icon="@drawable/ic_launcher_foreground"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
android:exported="false"
|
||||||
|
android:enabled="true"
|
||||||
android:description="@string/label_service_playback"
|
android:description="@string/label_service_playback"
|
||||||
android:stopWithTask="false" />
|
android:stopWithTask="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
|
@ -23,7 +23,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
|
|
||||||
Intent(this, PlaybackService::class.java).also {
|
Intent(this, PlaybackService::class.java).also {
|
||||||
this.startService(it)
|
startService(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.app.Notification
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
|
@ -12,12 +13,16 @@ import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
import org.oxycblt.auxio.music.coil.getBitmap
|
import org.oxycblt.auxio.music.coil.getBitmap
|
||||||
|
|
||||||
|
// Manager for the playback notification
|
||||||
|
// TODO: Implement some ability
|
||||||
internal class PlaybackNotificationHolder {
|
internal class PlaybackNotificationHolder {
|
||||||
private lateinit var mNotification: Notification
|
private lateinit var mNotification: Notification
|
||||||
|
|
||||||
private lateinit var notificationManager: NotificationManager
|
private lateinit var notificationManager: NotificationManager
|
||||||
private lateinit var baseNotification: NotificationCompat.Builder
|
private lateinit var baseNotification: NotificationCompat.Builder
|
||||||
|
|
||||||
|
private var isForeground = false
|
||||||
|
|
||||||
fun init(context: Context, session: MediaSessionCompat) {
|
fun init(context: Context, session: MediaSessionCompat) {
|
||||||
// Never run if the notification has already been created
|
// Never run if the notification has already been created
|
||||||
if (!::mNotification.isInitialized) {
|
if (!::mNotification.isInitialized) {
|
||||||
|
@ -28,7 +33,7 @@ internal class PlaybackNotificationHolder {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val channel = NotificationChannel(
|
val channel = NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
context.getString(R.string.label_notif_playback),
|
context.getString(R.string.label_notification_playback),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
notificationManager.createNotificationChannel(channel)
|
notificationManager.createNotificationChannel(channel)
|
||||||
|
@ -37,6 +42,7 @@ internal class PlaybackNotificationHolder {
|
||||||
baseNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
baseNotification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(R.drawable.ic_song)
|
.setSmallIcon(R.drawable.ic_song)
|
||||||
.setStyle(MediaStyle().setMediaSession(session.sessionToken))
|
.setStyle(MediaStyle().setMediaSession(session.sessionToken))
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
.setChannelId(CHANNEL_ID)
|
.setChannelId(CHANNEL_ID)
|
||||||
.setShowWhen(false)
|
.setShowWhen(false)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
@ -48,7 +54,7 @@ internal class PlaybackNotificationHolder {
|
||||||
fun setMetadata(song: Song, playbackService: PlaybackService) {
|
fun setMetadata(song: Song, playbackService: PlaybackService) {
|
||||||
// Set the basic metadata since MediaStyle wont do it yourself.
|
// Set the basic metadata since MediaStyle wont do it yourself.
|
||||||
// Fun Fact: The documentation still says that MediaStyle will handle metadata changes
|
// Fun Fact: The documentation still says that MediaStyle will handle metadata changes
|
||||||
// from MediaSession, even though it doesn't. After 6 years.
|
// from MediaSession, even though it doesn't. Its been 6 years. Fun.
|
||||||
baseNotification
|
baseNotification
|
||||||
.setContentTitle(song.name)
|
.setContentTitle(song.name)
|
||||||
.setContentText(
|
.setContentText(
|
||||||
|
@ -62,8 +68,27 @@ internal class PlaybackNotificationHolder {
|
||||||
getBitmap(song, playbackService) {
|
getBitmap(song, playbackService) {
|
||||||
baseNotification.setLargeIcon(it)
|
baseNotification.setLargeIcon(it)
|
||||||
mNotification = baseNotification.build()
|
mNotification = baseNotification.build()
|
||||||
|
|
||||||
|
if (!isForeground) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
playbackService.startForeground(
|
||||||
|
NOTIFICATION_ID, mNotification,
|
||||||
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
|
||||||
|
)
|
||||||
|
} else {
|
||||||
playbackService.startForeground(NOTIFICATION_ID, mNotification)
|
playbackService.startForeground(NOTIFICATION_ID, mNotification)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
notificationManager.notify(NOTIFICATION_ID, mNotification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop(playbackService: PlaybackService) {
|
||||||
|
playbackService.stopForeground(true)
|
||||||
|
notificationManager.cancel(NOTIFICATION_ID)
|
||||||
|
|
||||||
|
isForeground = false
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -129,7 +129,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
stopForeground(true)
|
notificationHolder.stop(this)
|
||||||
unregisterReceiver(systemReceiver)
|
unregisterReceiver(systemReceiver)
|
||||||
|
|
||||||
// Release everything that could cause a memory leak if left around
|
// Release everything that could cause a memory leak if left around
|
||||||
|
@ -202,7 +202,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
||||||
|
|
||||||
// Stop playing/the notification if there's nothing to play.
|
// Stop playing/the notification if there's nothing to play.
|
||||||
player.stop()
|
player.stop()
|
||||||
stopForeground(true)
|
notificationHolder.stop(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlayingUpdate(isPlaying: Boolean) {
|
override fun onPlayingUpdate(isPlaying: Boolean) {
|
||||||
|
@ -214,9 +214,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback {
|
||||||
startPollingPosition()
|
startPollingPosition()
|
||||||
} else {
|
} else {
|
||||||
player.pause()
|
player.pause()
|
||||||
|
|
||||||
// Be a polite service and stop being foreground if nothing is playing.
|
|
||||||
stopForeground(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ internal class PlaybackStateManager {
|
||||||
field = value
|
field = value
|
||||||
callbacks.forEach { it.onSongUpdate(value) }
|
callbacks.forEach { it.onSongUpdate(value) }
|
||||||
}
|
}
|
||||||
private var mPosition: Long = 0 // TODO: Consider using millis instead of seconds?
|
private var mPosition: Long = 0
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
callbacks.forEach { it.onPositionUpdate(value) }
|
callbacks.forEach { it.onPositionUpdate(value) }
|
||||||
|
|
|
@ -27,9 +27,8 @@
|
||||||
<string name="label_play">Play</string>
|
<string name="label_play">Play</string>
|
||||||
<string name="label_queue">Queue</string>
|
<string name="label_queue">Queue</string>
|
||||||
<string name="label_queue_add">Add to queue</string>
|
<string name="label_queue_add">Add to queue</string>
|
||||||
<string name="label_notif_playback">Music Playback</string>
|
<string name="label_notification_playback">Music Playback</string>
|
||||||
<string name="label_service_playback">The music playback service for Auxio.</string>
|
<string name="label_service_playback">The music playback service for Auxio.</string>
|
||||||
<string name="label_is_playing">Auxio is playing music</string>
|
|
||||||
|
|
||||||
<!-- Hint Namespace | EditText Hints -->
|
<!-- Hint Namespace | EditText Hints -->
|
||||||
<string name="hint_search_library">Search Library…</string>
|
<string name="hint_search_library">Search Library…</string>
|
||||||
|
@ -50,7 +49,7 @@
|
||||||
<string name="description_skip_prev">Skip to last song</string>
|
<string name="description_skip_prev">Skip to last song</string>
|
||||||
<string name="description_shuffle_on">Turn shuffle on</string>
|
<string name="description_shuffle_on">Turn shuffle on</string>
|
||||||
<string name="description_shuffle_off">Turn shuffle off</string>
|
<string name="description_shuffle_off">Turn shuffle off</string>
|
||||||
<string name="description_loop">Change Loop Mode</string>
|
<string name="description_loop">Loop</string>
|
||||||
|
|
||||||
<!-- Placeholder Namespace | Placeholder values -->
|
<!-- Placeholder Namespace | Placeholder values -->
|
||||||
<string name="placeholder_genre">Unknown Genre</string>
|
<string name="placeholder_genre">Unknown Genre</string>
|
||||||
|
|
Loading…
Reference in a new issue