From 7524589969e8cbd8d2a48189a8c9df6833a15aef Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Fri, 19 Feb 2021 15:54:27 -0700 Subject: [PATCH] Unify notification Unify the notification extensions into a single PlaybackNotification object that does not rely on PlaybackStateManager and SettingsManager. --- app/src/main/AndroidManifest.xml | 2 +- .../java/org/oxycblt/auxio/MainActivity.kt | 4 +- .../java/org/oxycblt/auxio/MainFragment.kt | 2 + .../auxio/playback/NotificationUtils.kt | 221 ------------------ .../auxio/playback/PlaybackViewModel.kt | 6 +- .../playback/state/PlaybackStateManager.kt | 5 +- .../playback/{ => system}/AudioReactor.kt | 2 +- .../playback/system/PlaybackNotification.kt | 219 +++++++++++++++++ .../playback/{ => system}/PlaybackService.kt | 110 +++++---- app/src/main/res/values-de/strings.xml | 1 + info/ARCHITECTURE.md | 3 +- 11 files changed, 295 insertions(+), 280 deletions(-) delete mode 100644 app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt rename app/src/main/java/org/oxycblt/auxio/playback/{ => system}/AudioReactor.kt (98%) create mode 100644 app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt rename app/src/main/java/org/oxycblt/auxio/playback/{ => system}/PlaybackService.kt (86%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 903bb3877..f86d3b8ce 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -42,7 +42,7 @@ = Build.VERSION_CODES.O) { - val channel = NotificationChannel( - NotificationUtils.CHANNEL_ID, - context.getString(R.string.info_channel_name), - NotificationManager.IMPORTANCE_DEFAULT - ) - - createNotificationChannel(channel) - } - - val mainIntent = PendingIntent.getActivity( - context, NotificationUtils.REQUEST_CODE, - Intent(context, MainActivity::class.java), - PendingIntent.FLAG_UPDATE_CURRENT - ) - - return NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID) - .setSmallIcon(R.drawable.ic_song) - .setStyle( - MediaStyle() - .setMediaSession(mediaSession.sessionToken) - .setShowActionsInCompactView(1, 2, 3) - ) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setChannelId(NotificationUtils.CHANNEL_ID) - .setShowWhen(false) - .addAction(newAction(NotificationUtils.ACTION_LOOP, context)) - .addAction(newAction(NotificationUtils.ACTION_SKIP_PREV, context)) - .addAction(newAction(NotificationUtils.ACTION_PLAY_PAUSE, context)) - .addAction(newAction(NotificationUtils.ACTION_SKIP_NEXT, context)) - .addAction(newAction(NotificationUtils.ACTION_EXIT, context)) - .setNotificationSilent() - .setContentIntent(mainIntent) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) -} - -/** - * Set the current metadata of a media notification. - * @param context The [Context] needed to load the cover bitmap - * @param song The [Song] that the notification should reflect - * @param colorize Whether to load the album art and colorize the notification based off it - * @param onDone A callback for when the process is finished - * @author OxygenCobalt - */ -fun NotificationCompat.Builder.setMetadata( - context: Context, - song: Song, - colorize: Boolean, - onDone: () -> Unit -) { - setContentTitle(song.name) - setContentText( - song.album.artist.name, - ) - - // On older versions of android [API <24], show the song's album on the subtext instead of - // the current mode, as that makes more sense for the old style of media notifications. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - setSubText(song.album.name) - } - - if (colorize) { - // getBitmap() is concurrent, so only call back to the object calling this function when - // the loading is over. - loadBitmap(context, song) { - setLargeIcon(it) - - onDone() - } - } else { - setLargeIcon(null) - - onDone() - } -} - -/** - * Update the playing button on the media notification. - * @param context The context required to refresh the action - */ -@SuppressLint("RestrictedApi") -fun NotificationCompat.Builder.updatePlaying(context: Context) { - mActions[2] = newAction(NotificationUtils.ACTION_PLAY_PAUSE, context) -} - -/** - * Update the extra action on the media notification [E.G the Loop/Shuffle button] - * @param context The context required to refresh the action - * @param useAltAction Whether to use the shuffle action or not, true if yes, false if no - */ -@SuppressLint("RestrictedApi") -fun NotificationCompat.Builder.updateExtraAction(context: Context, useAltAction: Boolean) { - mActions[0] = if (useAltAction) { - newAction(NotificationUtils.ACTION_SHUFFLE, context) - } else { - newAction(NotificationUtils.ACTION_LOOP, context) - } -} - -/** - * Update the subtext of the media notification to reflect the current mode. - * @param context The context required to get the strings required to show certain modes - */ -fun NotificationCompat.Builder.updateMode(context: Context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - val playbackManager = PlaybackStateManager.getInstance() - - // If playing from all songs, set the subtext as that, otherwise the currently played parent. - if (playbackManager.mode == PlaybackMode.ALL_SONGS) { - setSubText(context.getString(R.string.label_all_songs)) - } else { - val parent = playbackManager.parent - - if (parent != null) { - setSubText(if (parent is Genre) parent.displayName else parent.name) - } else { - logE("Parent was null when it shouldnt have been.") - setSubText("") - } - } - } -} - -/** - * Create a new [NotificationCompat.Action]. - * @param action The action that the notification action should represent - * @param context The [Context] needed to create the action - */ -private fun newAction(action: String, context: Context): NotificationCompat.Action { - val playbackManager = PlaybackStateManager.getInstance() - - // Get the icon depending on the action & current state. - val drawable = when (action) { - NotificationUtils.ACTION_LOOP -> { - when (playbackManager.loopMode) { - LoopMode.NONE -> R.drawable.ic_loop_inactive - LoopMode.ONCE -> R.drawable.ic_loop_one - LoopMode.INFINITE -> R.drawable.ic_loop - } - } - - NotificationUtils.ACTION_SHUFFLE -> { - if (playbackManager.isShuffling) { - R.drawable.ic_shuffle - } else { - R.drawable.ic_shuffle_inactive - } - } - - NotificationUtils.ACTION_SKIP_PREV -> R.drawable.ic_skip_prev - - NotificationUtils.ACTION_PLAY_PAUSE -> { - if (playbackManager.isPlaying) { - R.drawable.ic_pause - } else { - R.drawable.ic_play - } - } - - NotificationUtils.ACTION_SKIP_NEXT -> R.drawable.ic_skip_next - - NotificationUtils.ACTION_EXIT -> R.drawable.ic_exit - else -> R.drawable.ic_error - } - - return NotificationCompat.Action.Builder( - drawable, action, - PendingIntent.getBroadcast( - context, NotificationUtils.REQUEST_CODE, - Intent(action), PendingIntent.FLAG_UPDATE_CURRENT - ) - ).build() -} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 0ce02efda..89b6acc22 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -114,7 +114,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { /** * Play a song. * @param song The song to be played - * @param mode The [PlaybackMode] for it to be played in. Defaults to the preferred song playback mode if not specified. + * @param mode The [PlaybackMode] for it to be played in. Defaults to the preferred song playback mode of the user if not specified. */ fun playSong(song: Song, mode: PlaybackMode = settingsManager.songPlaybackMode) { playbackManager.playSong(song, mode) @@ -413,8 +413,8 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mIsShuffling.value = isShuffling } - override fun onLoopUpdate(mode: LoopMode) { - mLoopMode.value = mode + override fun onLoopUpdate(loopMode: LoopMode) { + mLoopMode.value = loopMode } override fun onInUserQueueUpdate(isInUserQueue: Boolean) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 20e1fd62a..ca36d9316 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -22,7 +22,7 @@ import org.oxycblt.auxio.settings.SettingsManager * * This should ***NOT*** be used outside of the playback module. * - If you want to use the playback state in the UI, use [org.oxycblt.auxio.playback.PlaybackViewModel] as it can withstand volatile UIs. - * - If you want to use the playback state with the ExoPlayer instance or system-side things, use [org.oxycblt.auxio.playback.PlaybackService]. + * - If you want to use the playback state with the ExoPlayer instance or system-side things, use [org.oxycblt.auxio.playback.system.PlaybackService]. * * All access should be done with [PlaybackStateManager.getInstance]. * @@ -454,6 +454,7 @@ class PlaybackStateManager private constructor() { fun shuffleAll() { mMode = PlaybackMode.ALL_SONGS mQueue = musicStore.songs.toMutableList() + mParent = null setShuffling(true, keepSong = false) updatePlayback(mQueue[0]) @@ -809,7 +810,7 @@ class PlaybackStateManager private constructor() { fun onIndexUpdate(index: Int) {} fun onPlayingUpdate(isPlaying: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {} - fun onLoopUpdate(mode: LoopMode) {} + fun onLoopUpdate(loopMode: LoopMode) {} fun onSeek(position: Long) {} fun onInUserQueueUpdate(isInUserQueue: Boolean) {} } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/AudioReactor.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/playback/AudioReactor.kt rename to app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt index 9a6121aba..ed02c3440 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/AudioReactor.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/AudioReactor.kt @@ -1,4 +1,4 @@ -package org.oxycblt.auxio.playback +package org.oxycblt.auxio.playback.system import android.animation.ValueAnimator import android.content.Context diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt new file mode 100644 index 000000000..a36440621 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackNotification.kt @@ -0,0 +1,219 @@ +package org.oxycblt.auxio.playback.system + +import android.annotation.SuppressLint +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Build +import android.support.v4.media.session.MediaSessionCompat +import androidx.annotation.DrawableRes +import androidx.core.app.NotificationCompat +import org.oxycblt.auxio.BuildConfig +import org.oxycblt.auxio.MainActivity +import org.oxycblt.auxio.R +import org.oxycblt.auxio.coil.loadBitmap +import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.Parent +import org.oxycblt.auxio.music.Song +import org.oxycblt.auxio.playback.state.LoopMode +import org.oxycblt.auxio.playback.state.PlaybackStateManager +import androidx.media.app.NotificationCompat as MediaNotificationCompat + +/** + * The unified notification for [PlaybackService]. This is not self-sufficient, updates have + * to be delivered manually. + * @author OxygenCobalt + */ +@SuppressLint("RestrictedApi") +class PlaybackNotification private constructor( + context: Context, + mediaToken: MediaSessionCompat.Token +) : NotificationCompat.Builder(context, CHANNEL_ID), PlaybackStateManager.Callback { + init { + val mainActivityIntent = PendingIntent.getActivity( + context, REQUEST_CODE, + Intent(context, MainActivity::class.java), + PendingIntent.FLAG_UPDATE_CURRENT + ) + + setSmallIcon(R.drawable.ic_song) + setCategory(NotificationCompat.CATEGORY_SERVICE) + setShowWhen(false) + setNotificationSilent() + setContentIntent(mainActivityIntent) + setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + + addAction(buildLoopAction(context, LoopMode.NONE)) + addAction(buildAction(context, ACTION_SKIP_PREV, R.drawable.ic_skip_prev)) + addAction(buildPlayPauseAction(context, true)) + addAction(buildAction(context, ACTION_SKIP_NEXT, R.drawable.ic_skip_next)) + addAction(buildAction(context, ACTION_EXIT, R.drawable.ic_exit)) + + setStyle( + MediaNotificationCompat.MediaStyle() + .setMediaSession(mediaToken) + .setShowActionsInCompactView(1, 2, 3) + ) + } + + // --- STATE FUNCTIONS --- + + /** + * Set the metadata of the notification using [song]. + * @param colorize Whether to show the album art of [song] on the notification + * @param onDone What to do when the loading of the album art is finished + */ + fun setMetadata(context: Context, song: Song, colorize: Boolean, onDone: () -> Unit) { + setContentTitle(song.name) + setContentText(song.album.artist.name) + + // On older versions of android [API <24], show the song's album on the subtext instead of + // the current mode, as that makes more sense for the old style of media notifications. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + setSubText(song.album.name) + } + + if (colorize) { + // loadBitmap() is concurrent, so only call back to the object calling this function when + // the loading is over. + loadBitmap(context, song) { + setLargeIcon(it) + + onDone() + } + } else { + setLargeIcon(null) + + onDone() + } + } + + /** + * Set the playing icon on the notification + */ + fun setPlaying(context: Context, isPlaying: Boolean) { + mActions[2] = buildPlayPauseAction(context, isPlaying) + } + + /** + * Update the first action to reflect the [loopMode] given. + */ + fun setLoop(context: Context, loopMode: LoopMode) { + mActions[0] = buildLoopAction(context, loopMode) + } + + /** + * Update the first action to reflect whether the queue is shuffled or not + */ + fun setShuffle(context: Context, isShuffling: Boolean) { + mActions[0] = buildShuffleAction(context, isShuffling) + } + + /** + * Apply the current [parent] to the header of the notification. + */ + fun setParent(context: Context, parent: Parent?) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + return + } + + if (parent == null) { + // A blank parent always means that the mode is ALL_SONGS + setSubText(context.getString(R.string.label_all_songs)) + } else { + if (parent is Genre) { + // Use display name for genre + setSubText(parent.displayName) + } else { + setSubText(parent.name) + } + } + } + + // --- NOTIFICATION ACTION BUILDERS --- + + private fun buildPlayPauseAction( + context: Context, + isPlaying: Boolean + ): NotificationCompat.Action { + val drawableRes = if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play + + return buildAction(context, ACTION_PLAY_PAUSE, drawableRes) + } + + private fun buildLoopAction( + context: Context, + loopMode: LoopMode + ): NotificationCompat.Action { + val drawableRes = when (loopMode) { + LoopMode.NONE -> R.drawable.ic_loop_inactive + LoopMode.ONCE -> R.drawable.ic_loop_one + LoopMode.INFINITE -> R.drawable.ic_loop + } + + return buildAction(context, ACTION_LOOP, drawableRes) + } + + private fun buildShuffleAction( + context: Context, + isShuffled: Boolean + ): NotificationCompat.Action { + val drawableRes = if (isShuffled) R.drawable.ic_shuffle else R.drawable.ic_shuffle_inactive + + return buildAction(context, ACTION_SHUFFLE, drawableRes) + } + + private fun buildAction( + context: Context, + actionName: String, + @DrawableRes iconRes: Int + ): NotificationCompat.Action { + val action = NotificationCompat.Action.Builder( + iconRes, actionName, + PendingIntent.getBroadcast( + context, REQUEST_CODE, + Intent(actionName), PendingIntent.FLAG_UPDATE_CURRENT + ) + ) + + return action.build() + } + + companion object { + const val CHANNEL_ID = "CHANNEL_AUXIO_PLAYBACK" + const val NOTIFICATION_ID = 0xA0A0 + const val REQUEST_CODE = 0xA0C0 + + // Build type is added to the codes so that dual installations dont conflict + // with eachother. + const val ACTION_LOOP = "ACTION_AUXIO_LOOP_" + BuildConfig.BUILD_TYPE + const val ACTION_SHUFFLE = "ACTION_AUXIO_SHUFFLE_" + BuildConfig.BUILD_TYPE + const val ACTION_SKIP_PREV = "ACTION_AUXIO_SKIP_PREV_" + BuildConfig.BUILD_TYPE + const val ACTION_PLAY_PAUSE = "ACTION_AUXIO_PLAY_PAUSE_" + BuildConfig.BUILD_TYPE + const val ACTION_SKIP_NEXT = "ACTION_AUXIO_SKIP_NEXT_" + BuildConfig.BUILD_TYPE + const val ACTION_EXIT = "ACTION_AUXIO_EXIT_" + BuildConfig.BUILD_TYPE + + /** + * Build a new instance of [PlaybackNotification]. + */ + fun from(context: Context, mediaSession: MediaSessionCompat): PlaybackNotification { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Create the notification channel if required. + val notificationManager = context.getSystemService( + Context.NOTIFICATION_SERVICE + ) as NotificationManager + + val channel = NotificationChannel( + CHANNEL_ID, context.getString(R.string.info_channel_name), + NotificationManager.IMPORTANCE_DEFAULT + ) + + notificationManager.createNotificationChannel(channel) + } + + return PlaybackNotification(context, mediaSession.sessionToken) + } + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt similarity index 86% rename from app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt rename to app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt index 5cdb5e2c6..598795e33 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt @@ -1,4 +1,4 @@ -package org.oxycblt.auxio.playback +package org.oxycblt.auxio.playback.system import android.app.NotificationManager import android.app.Service @@ -15,7 +15,6 @@ import android.os.Parcelable import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat import android.view.KeyEvent -import androidx.core.app.NotificationCompat import com.google.android.exoplayer2.C import com.google.android.exoplayer2.ExoPlaybackException import com.google.android.exoplayer2.MediaItem @@ -40,10 +39,10 @@ import kotlinx.coroutines.flow.takeWhile import kotlinx.coroutines.launch import org.oxycblt.auxio.coil.loadBitmap import org.oxycblt.auxio.logD +import org.oxycblt.auxio.music.Parent import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.playback.state.LoopMode -import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.SettingsManager @@ -73,7 +72,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca .build() private lateinit var notificationManager: NotificationManager - private lateinit var notification: NotificationCompat.Builder + private lateinit var notification: PlaybackNotification private lateinit var audioReactor: AudioReactor private var isForeground = false @@ -124,12 +123,12 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca systemReceiver = SystemEventReceiver() IntentFilter().apply { - addAction(NotificationUtils.ACTION_LOOP) - addAction(NotificationUtils.ACTION_SHUFFLE) - addAction(NotificationUtils.ACTION_SKIP_PREV) - addAction(NotificationUtils.ACTION_PLAY_PAUSE) - addAction(NotificationUtils.ACTION_SKIP_NEXT) - addAction(NotificationUtils.ACTION_EXIT) + addAction(PlaybackNotification.ACTION_LOOP) + addAction(PlaybackNotification.ACTION_SHUFFLE) + addAction(PlaybackNotification.ACTION_SKIP_PREV) + addAction(PlaybackNotification.ACTION_PLAY_PAUSE) + addAction(PlaybackNotification.ACTION_SKIP_NEXT) + addAction(PlaybackNotification.ACTION_EXIT) addAction(BluetoothDevice.ACTION_ACL_CONNECTED) addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) @@ -142,7 +141,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca // --- NOTIFICATION SETUP --- notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - notification = notificationManager.createMediaNotification(this, mediaSession) + notification = PlaybackNotification.from(this, mediaSession) // --- PLAYBACKSTATEMANAGER SETUP --- @@ -235,8 +234,8 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca stopForegroundAndNotification() } - override fun onModeUpdate(mode: PlaybackMode) { - notification.updateMode(this) + override fun onParentUpdate(parent: Parent?) { + notification.setParent(this, parent) startForegroundOrNotify() } @@ -244,40 +243,40 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca override fun onPlayingUpdate(isPlaying: Boolean) { if (isPlaying && !player.isPlaying) { player.play() - notification.updatePlaying(this) audioReactor.requestFocus() - startForegroundOrNotify() - startPollingPosition() } else { player.pause() - notification.updatePlaying(this) - startForegroundOrNotify() - } - } - - override fun onLoopUpdate(mode: LoopMode) { - when (mode) { - LoopMode.NONE -> { - player.repeatMode = Player.REPEAT_MODE_OFF - } - else -> { - player.repeatMode = Player.REPEAT_MODE_ONE - } } - notification.updateExtraAction(this, settingsManager.useAltNotifAction) + notification.setPlaying(this, isPlaying) startForegroundOrNotify() } - override fun onShuffleUpdate(isShuffling: Boolean) { - if (settingsManager.useAltNotifAction) { - notification.updateExtraAction(this, settingsManager.useAltNotifAction) + override fun onLoopUpdate(loopMode: LoopMode) { + player.repeatMode = if (loopMode == LoopMode.NONE) { + Player.REPEAT_MODE_OFF + } else { + Player.REPEAT_MODE_ONE + } + + if (!settingsManager.useAltNotifAction) { + notification.setLoop(this, loopMode) startForegroundOrNotify() } } + override fun onShuffleUpdate(isShuffling: Boolean) { + if (!settingsManager.useAltNotifAction) { + return + } + + notification.setShuffle(this, isShuffling) + + startForegroundOrNotify() + } + override fun onSeek(position: Long) { player.seekTo(position) } @@ -293,7 +292,11 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } override fun onNotifActionUpdate(useAltAction: Boolean) { - notification.updateExtraAction(this, useAltAction) + if (useAltAction) { + notification.setShuffle(this, playbackManager.isShuffling) + } else { + notification.setLoop(this, playbackManager.loopMode) + } startForegroundOrNotify() } @@ -359,12 +362,17 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca * Restore the notification, if the service was destroyed while [PlaybackStateManager] persisted. */ private fun restoreNotification() { - notification.updateExtraAction(this, settingsManager.useAltNotifAction) - notification.updateMode(this) - notification.updatePlaying(this) + notification.setParent(this, playbackManager.parent) + notification.setPlaying(this, playbackManager.isPlaying) - playbackManager.song?.let { - notification.setMetadata(this, it, settingsManager.colorizeNotif) { + if (settingsManager.useAltNotifAction) { + notification.setShuffle(this, playbackManager.isShuffling) + } else { + notification.setLoop(this, playbackManager.loopMode) + } + + playbackManager.song?.let { song -> + notification.setMetadata(this, song, settingsManager.colorizeNotif) { if (playbackManager.isPlaying) { startForegroundOrNotify() } else { @@ -427,14 +435,18 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca if (!isForeground) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground( - NotificationUtils.NOTIFICATION_ID, notification.build(), + PlaybackNotification.NOTIFICATION_ID, notification.build(), ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK ) } else { - startForeground(NotificationUtils.NOTIFICATION_ID, notification.build()) + startForeground( + PlaybackNotification.NOTIFICATION_ID, notification.build() + ) } } else { - notificationManager.notify(NotificationUtils.NOTIFICATION_ID, notification.build()) + notificationManager.notify( + PlaybackNotification.NOTIFICATION_ID, notification.build() + ) } } } @@ -444,7 +456,7 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca */ private fun stopForegroundAndNotification() { stopForeground(true) - notificationManager.cancel(NotificationUtils.NOTIFICATION_ID) + notificationManager.cancel(PlaybackNotification.NOTIFICATION_ID) isForeground = false } @@ -504,19 +516,19 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca action?.let { when (it) { - NotificationUtils.ACTION_LOOP -> + PlaybackNotification.ACTION_LOOP -> playbackManager.setLoopMode(playbackManager.loopMode.increment()) - NotificationUtils.ACTION_SHUFFLE -> + PlaybackNotification.ACTION_SHUFFLE -> playbackManager.setShuffling(!playbackManager.isShuffling, keepSong = true) - NotificationUtils.ACTION_SKIP_PREV -> playbackManager.prev() + PlaybackNotification.ACTION_SKIP_PREV -> playbackManager.prev() - NotificationUtils.ACTION_PLAY_PAUSE -> + PlaybackNotification.ACTION_PLAY_PAUSE -> playbackManager.setPlaying(!playbackManager.isPlaying) - NotificationUtils.ACTION_SKIP_NEXT -> playbackManager.next() - NotificationUtils.ACTION_EXIT -> stop() + PlaybackNotification.ACTION_SKIP_NEXT -> playbackManager.next() + PlaybackNotification.ACTION_EXIT -> stop() BluetoothDevice.ACTION_ACL_CONNECTED -> resume() BluetoothDevice.ACTION_ACL_DISCONNECTED -> pause() diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4be8ecf11..c44c23d0e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1,6 +1,7 @@ + Ein einfacher, sinnvoller Musikplayer für android. Musikwiedergabe der Musikwiedergabedienst von Auxio. diff --git a/info/ARCHITECTURE.md b/info/ARCHITECTURE.md index 20f3aa81b..b052ae362 100644 --- a/info/ARCHITECTURE.md +++ b/info/ARCHITECTURE.md @@ -45,7 +45,8 @@ org.oxycblt.auxio # Main UI's and logging utilities │ └──.processing # Systems for music loading and organization ├──.playback # Playback UI and systems │ ├──.queue # Queue user interface -│ └──.state # Backend/Modes for the playback state +│ ├──.state # Backend/Modes for the playback state +│ └──.system # System-side playback [Services, ExoPlayer] ├──.recycler # Shared RecyclerView utilities and modes │ └──.viewholders # Shared ViewHolders and ViewHolder utilities ├──.search # Search UI