diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f7fa7b198..c657ec84b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -77,22 +77,12 @@
-
-
diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
index 3fa2ad852..f997f3b0c 100644
--- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
+++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt
@@ -29,10 +29,9 @@ import androidx.core.view.updatePadding
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.oxycblt.auxio.databinding.ActivityMainBinding
-import org.oxycblt.auxio.music.system.IndexerService
import org.oxycblt.auxio.playback.PlaybackViewModel
import org.oxycblt.auxio.playback.state.DeferredPlayback
-import org.oxycblt.auxio.playback.system.PlaybackService
+import org.oxycblt.auxio.service.AuxioService
import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.util.isNight
import org.oxycblt.auxio.util.logD
@@ -71,8 +70,7 @@ class MainActivity : AppCompatActivity() {
override fun onResume() {
super.onResume()
- startService(Intent(this, IndexerService::class.java))
- startService(Intent(this, PlaybackService::class.java))
+ startService(Intent(this, AuxioService::class.java))
if (!startIntentAction(intent)) {
// No intent action to do, just restore the previously saved state.
diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
index 459c8fcbb..e1b6e2442 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt
@@ -206,7 +206,7 @@ interface MusicRepository {
/** A persistent worker that can load music in the background. */
interface IndexingWorker {
/** A [Context] required to read device storage */
- val context: Context
+ val applicationContext: Context
/** The [CoroutineScope] to perform coroutine music loading work on. */
val scope: CoroutineScope
@@ -343,7 +343,7 @@ constructor(
}
override fun index(worker: MusicRepository.IndexingWorker, withCache: Boolean) =
- worker.scope.launch { indexWrapper(worker.context, this, withCache) }
+ worker.scope.launch { indexWrapper(worker.applicationContext, this, withCache) }
private suspend fun indexWrapper(context: Context, scope: CoroutineScope, withCache: Boolean) {
try {
diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt
similarity index 99%
rename from app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt
rename to app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt
index e94e4fe16..c70707375 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerNotifications.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.music.system
+package org.oxycblt.auxio.music.service
import android.content.Context
import android.os.SystemClock
diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
similarity index 85%
rename from app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt
rename to app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
index 83f8d5f80..06fd193e4 100644
--- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt
+++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexerServiceFragment.kt
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2022 Auxio Project
- * IndexerService.kt is part of Auxio.
+ * IndexerServiceFragment.kt is part of Auxio.
*
* 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
@@ -16,18 +16,16 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.music.system
+package org.oxycblt.auxio.music.service
import android.app.Service
-import android.content.Intent
+import android.content.Context
import android.database.ContentObserver
import android.os.Handler
-import android.os.IBinder
import android.os.Looper
import android.os.PowerManager
import android.provider.MediaStore
import coil.ImageLoader
-import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -39,7 +37,7 @@ import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.music.fs.contentResolverSafe
import org.oxycblt.auxio.playback.state.PlaybackStateManager
-import org.oxycblt.auxio.service.ForegroundManager
+import org.oxycblt.auxio.service.ServiceFragment
import org.oxycblt.auxio.util.getSystemServiceCompat
import org.oxycblt.auxio.util.logD
@@ -57,35 +55,33 @@ import org.oxycblt.auxio.util.logD
*
* TODO: Unify with PlaybackService as part of the service independence project
*/
-@AndroidEntryPoint
-class IndexerService :
- Service(),
+class IndexerServiceFragment
+@Inject
+constructor(
+ val imageLoader: ImageLoader,
+ val musicRepository: MusicRepository,
+ val musicSettings: MusicSettings,
+ val playbackManager: PlaybackStateManager
+) :
+ ServiceFragment(),
MusicRepository.IndexingWorker,
MusicRepository.IndexingListener,
MusicRepository.UpdateListener,
MusicSettings.Listener {
- @Inject lateinit var imageLoader: ImageLoader
- @Inject lateinit var musicRepository: MusicRepository
- @Inject lateinit var musicSettings: MusicSettings
- @Inject lateinit var playbackManager: PlaybackStateManager
-
private val serviceJob = Job()
private val indexScope = CoroutineScope(serviceJob + Dispatchers.IO)
private var currentIndexJob: Job? = null
- private lateinit var foregroundManager: ForegroundManager
private lateinit var indexingNotification: IndexingNotification
private lateinit var observingNotification: ObservingNotification
private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var indexerContentObserver: SystemContentObserver
- override fun onCreate() {
- super.onCreate()
- // Initialize the core service components first.
- foregroundManager = ForegroundManager(this)
- indexingNotification = IndexingNotification(this)
- observingNotification = ObservingNotification(this)
+ override fun onCreate(context: Context) {
+ indexingNotification = IndexingNotification(context)
+ observingNotification = ObservingNotification(context)
wakeLock =
- getSystemServiceCompat(PowerManager::class)
+ context
+ .getSystemServiceCompat(PowerManager::class)
.newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, BuildConfig.APPLICATION_ID + ":IndexerService")
// Initialize any listener-dependent components last as we wouldn't want a listener race
@@ -99,14 +95,8 @@ class IndexerService :
logD("Service created.")
}
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_NOT_STICKY
-
- override fun onBind(intent: Intent?): IBinder? = null
-
override fun onDestroy() {
- super.onDestroy()
// De-initialize core service components first.
- foregroundManager.release()
wakeLock.releaseSafe()
// Then cancel the listener-dependent components to ensure that stray reloading
// events will not occur.
@@ -126,10 +116,11 @@ class IndexerService :
// Cancel the previous music loading job.
currentIndexJob?.cancel()
// Start a new music loading job on a co-routine.
- currentIndexJob = musicRepository.index(this@IndexerService, withCache)
+ currentIndexJob = musicRepository.index(this, withCache)
}
- override val context = this
+ override val applicationContext: Context
+ get() = context
override val scope = indexScope
@@ -169,9 +160,9 @@ class IndexerService :
// notification when initially starting, we will not update the notification
// unless it indicates that it has changed.
val changed = indexingNotification.updateIndexingState(progress)
- if (!foregroundManager.tryStartForeground(indexingNotification) && changed) {
+ if (changed) {
logD("Notification changed, re-posting notification")
- indexingNotification.post()
+ startForeground(indexingNotification)
}
// Make sure we can keep the CPU on while loading music
wakeLock.acquireSafe()
@@ -188,14 +179,11 @@ class IndexerService :
// TODO: Assuming I unify this with PlaybackService, it's possible that I won't need
// this anymore, or at least I only have to use it when the app task is not removed.
logD("Need to observe, staying in foreground")
- if (!foregroundManager.tryStartForeground(observingNotification)) {
- logD("Notification changed, re-posting notification")
- observingNotification.post()
- }
+ startForeground(observingNotification)
} else {
// Not observing and done loading, exit foreground.
logD("Exiting foreground")
- foregroundManager.tryStopForeground()
+ stopForeground()
}
// Release our wake lock (if we were using it)
wakeLock.releaseSafe()
@@ -250,7 +238,7 @@ class IndexerService :
private val handler = Handler(Looper.getMainLooper())
init {
- contentResolverSafe.registerContentObserver(
+ context.contentResolverSafe.registerContentObserver(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this)
}
@@ -260,7 +248,7 @@ class IndexerService :
*/
fun release() {
handler.removeCallbacks(this)
- contentResolverSafe.unregisterContentObserver(this)
+ context.contentResolverSafe.unregisterContentObserver(this)
}
override fun onChange(selfChange: Boolean) {
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/BetterShuffleOrder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/BetterShuffleOrder.kt
similarity index 98%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/BetterShuffleOrder.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/BetterShuffleOrder.kt
index e09d9ba2a..fe5c8628b 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/BetterShuffleOrder.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/BetterShuffleOrder.kt
@@ -16,11 +16,10 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import androidx.media3.common.C
import androidx.media3.exoplayer.source.ShuffleOrder
-import java.util.*
/**
* A ShuffleOrder that fixes the poorly defined default implementation of cloneAndInsert. Whereas
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/BluetoothHeadsetReceiver.kt
similarity index 97%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/BluetoothHeadsetReceiver.kt
index c8dbabc83..11df166f2 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/BluetoothHeadsetReceiver.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/BluetoothHeadsetReceiver.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaButtonReceiver.kt
similarity index 94%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/MediaButtonReceiver.kt
index 83b6bcbcd..f2281ac49 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaButtonReceiver.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaButtonReceiver.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import android.content.BroadcastReceiver
import android.content.ComponentName
@@ -29,7 +29,8 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.util.logD
/**
- * A [BroadcastReceiver] that forwards [Intent.ACTION_MEDIA_BUTTON] [Intent]s to [PlaybackService].
+ * A [BroadcastReceiver] that forwards [Intent.ACTION_MEDIA_BUTTON] [Intent]s to
+ * [PlaybackServiceFragment].
*
* @author Alexander Capehart (OxygenCobalt)
*/
@@ -46,7 +47,7 @@ class MediaButtonReceiver : BroadcastReceiver() {
// wrong action at the wrong time will result in the app crashing, and there is
// nothing I can do about it.
logD("Delivering media button intent $intent")
- intent.component = ComponentName(context, PlaybackService::class.java)
+ intent.component = ComponentName(context, PlaybackServiceFragment::class.java)
ContextCompat.startForegroundService(context, intent)
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionComponent.kt
similarity index 98%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionComponent.kt
index cbccf56ec..61720d277 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/MediaSessionComponent.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionComponent.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import android.content.Context
import android.content.Intent
@@ -275,7 +275,7 @@ constructor(
override fun onStop() {
// Get the service to shut down with the ACTION_EXIT intent
- context.sendBroadcast(Intent(PlaybackService.ACTION_EXIT))
+ context.sendBroadcast(Intent(PlaybackServiceFragment.ACTION_EXIT))
}
// --- INTERNAL ---
@@ -403,7 +403,7 @@ constructor(
ActionMode.SHUFFLE -> {
logD("Using shuffle MediaSession action")
PlaybackStateCompat.CustomAction.Builder(
- PlaybackService.ACTION_INVERT_SHUFFLE,
+ PlaybackServiceFragment.ACTION_INVERT_SHUFFLE,
context.getString(R.string.desc_shuffle),
if (playbackManager.isShuffled) {
R.drawable.ic_shuffle_on_24
@@ -414,7 +414,7 @@ constructor(
else -> {
logD("Using repeat mode MediaSession action")
PlaybackStateCompat.CustomAction.Builder(
- PlaybackService.ACTION_INC_REPEAT_MODE,
+ PlaybackServiceFragment.ACTION_INC_REPEAT_MODE,
context.getString(R.string.desc_change_repeat),
playbackManager.repeatMode.icon)
}
@@ -424,7 +424,7 @@ constructor(
// Add the exit action so the service can be closed
val exitAction =
PlaybackStateCompat.CustomAction.Builder(
- PlaybackService.ACTION_EXIT,
+ PlaybackServiceFragment.ACTION_EXIT,
context.getString(R.string.desc_exit),
R.drawable.ic_close_24)
.build()
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/NotificationComponent.kt
similarity index 90%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/NotificationComponent.kt
index 7b9868072..c441f8bff 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/NotificationComponent.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/NotificationComponent.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import android.annotation.SuppressLint
import android.content.Context
@@ -53,11 +53,13 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
addAction(buildRepeatAction(context, RepeatMode.NONE))
addAction(
- buildAction(context, PlaybackService.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24))
+ buildAction(
+ context, PlaybackServiceFragment.ACTION_SKIP_PREV, R.drawable.ic_skip_prev_24))
addAction(buildPlayPauseAction(context, true))
addAction(
- buildAction(context, PlaybackService.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24))
- addAction(buildAction(context, PlaybackService.ACTION_EXIT, R.drawable.ic_close_24))
+ buildAction(
+ context, PlaybackServiceFragment.ACTION_SKIP_NEXT, R.drawable.ic_skip_next_24))
+ addAction(buildAction(context, PlaybackServiceFragment.ACTION_EXIT, R.drawable.ic_close_24))
setStyle(MediaStyle().setMediaSession(sessionToken).setShowActionsInCompactView(1, 2, 3))
}
@@ -122,14 +124,14 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
} else {
R.drawable.ic_play_24
}
- return buildAction(context, PlaybackService.ACTION_PLAY_PAUSE, drawableRes)
+ return buildAction(context, PlaybackServiceFragment.ACTION_PLAY_PAUSE, drawableRes)
}
private fun buildRepeatAction(
context: Context,
repeatMode: RepeatMode
): NotificationCompat.Action {
- return buildAction(context, PlaybackService.ACTION_INC_REPEAT_MODE, repeatMode.icon)
+ return buildAction(context, PlaybackServiceFragment.ACTION_INC_REPEAT_MODE, repeatMode.icon)
}
private fun buildShuffleAction(
@@ -142,7 +144,7 @@ class NotificationComponent(private val context: Context, sessionToken: MediaSes
} else {
R.drawable.ic_shuffle_off_24
}
- return buildAction(context, PlaybackService.ACTION_INVERT_SHUFFLE, drawableRes)
+ return buildAction(context, PlaybackServiceFragment.ACTION_INVERT_SHUFFLE, drawableRes)
}
private fun buildAction(context: Context, actionName: String, @DrawableRes iconRes: Int) =
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackServiceFragment.kt
similarity index 93%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackServiceFragment.kt
index ba54a1f18..f30d1f727 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/PlaybackService.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackServiceFragment.kt
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Auxio Project
- * PlaybackService.kt is part of Auxio.
+ * PlaybackServiceFragment.kt is part of Auxio.
*
* 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
@@ -16,16 +16,14 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
-import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import android.media.audiofx.AudioEffect
-import android.os.IBinder
import androidx.core.content.ContextCompat
import androidx.media3.common.AudioAttributes
import androidx.media3.common.C
@@ -39,7 +37,6 @@ import androidx.media3.exoplayer.audio.AudioCapabilities
import androidx.media3.exoplayer.audio.MediaCodecAudioRenderer
import androidx.media3.exoplayer.mediacodec.MediaCodecSelector
import androidx.media3.exoplayer.source.MediaSource
-import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -63,7 +60,7 @@ import org.oxycblt.auxio.playback.state.Progression
import org.oxycblt.auxio.playback.state.RawQueue
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.playback.state.StateAck
-import org.oxycblt.auxio.service.ForegroundManager
+import org.oxycblt.auxio.service.ServiceFragment
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logE
import org.oxycblt.auxio.widgets.WidgetComponent
@@ -85,9 +82,20 @@ import org.oxycblt.auxio.widgets.WidgetProvider
* TODO: Refactor lifecycle to run completely headless (i.e no activity needed)
* TODO: Android Auto
*/
-@AndroidEntryPoint
-class PlaybackService :
- Service(),
+class PlaybackServiceFragment
+@Inject
+constructor(
+ val mediaSourceFactory: MediaSource.Factory,
+ val replayGainProcessor: ReplayGainAudioProcessor,
+ val mediaSessionComponent: MediaSessionComponent,
+ val widgetComponent: WidgetComponent,
+ val playbackManager: PlaybackStateManager,
+ val playbackSettings: PlaybackSettings,
+ val persistenceRepository: PersistenceRepository,
+ val listSettings: ListSettings,
+ val musicRepository: MusicRepository
+) :
+ ServiceFragment(),
Player.Listener,
PlaybackStateHolder,
PlaybackSettings.Listener,
@@ -95,23 +103,11 @@ class PlaybackService :
MusicRepository.UpdateListener {
// Player components
private lateinit var player: ExoPlayer
- @Inject lateinit var mediaSourceFactory: MediaSource.Factory
- @Inject lateinit var replayGainProcessor: ReplayGainAudioProcessor
// System backend components
- @Inject lateinit var mediaSessionComponent: MediaSessionComponent
- @Inject lateinit var widgetComponent: WidgetComponent
private val systemReceiver = PlaybackReceiver()
- // Shared components
- @Inject lateinit var playbackManager: PlaybackStateManager
- @Inject lateinit var playbackSettings: PlaybackSettings
- @Inject lateinit var persistenceRepository: PersistenceRepository
- @Inject lateinit var listSettings: ListSettings
- @Inject lateinit var musicRepository: MusicRepository
-
- // State
- private lateinit var foregroundManager: ForegroundManager
+ // Stat
private var hasPlayed = false
private var openAudioEffectSession = false
@@ -123,16 +119,14 @@ class PlaybackService :
// --- SERVICE OVERRIDES ---
- override fun onCreate() {
- super.onCreate()
-
+ override fun onCreate(context: Context) {
// Since Auxio is a music player, only specify an audio renderer to save
// battery/apk size/cache size
val audioRenderer = RenderersFactory { handler, _, audioListener, _, _ ->
arrayOf(
FfmpegAudioRenderer(handler, audioListener, replayGainProcessor),
MediaCodecAudioRenderer(
- this,
+ context,
MediaCodecSelector.DEFAULT,
handler,
audioListener,
@@ -141,7 +135,7 @@ class PlaybackService :
}
player =
- ExoPlayer.Builder(this, audioRenderer)
+ ExoPlayer.Builder(context, audioRenderer)
.setMediaSourceFactory(mediaSourceFactory)
// Enable automatic WakeLock support
.setWakeMode(C.WAKE_MODE_LOCAL)
@@ -154,7 +148,6 @@ class PlaybackService :
true)
.build()
.also { it.addListener(this) }
- foregroundManager = ForegroundManager(this)
// Initialize any listener-dependent components last as we wouldn't want a listener race
// condition to cause us to load music before we were fully initialize.
playbackManager.registerStateHolder(this)
@@ -176,23 +169,19 @@ class PlaybackService :
}
ContextCompat.registerReceiver(
- this, systemReceiver, intentFilter, ContextCompat.RECEIVER_EXPORTED)
+ context, systemReceiver, intentFilter, ContextCompat.RECEIVER_EXPORTED)
logD("Service created")
}
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ override fun onStartCommand(intent: Intent) {
// Forward system media button sent by MediaButtonReceiver to MediaSessionComponent
if (intent.action == Intent.ACTION_MEDIA_BUTTON) {
mediaSessionComponent.handleMediaButtonIntent(intent)
}
- return START_NOT_STICKY
}
- override fun onBind(intent: Intent): IBinder? = null
-
- override fun onTaskRemoved(rootIntent: Intent?) {
- super.onTaskRemoved(rootIntent)
+ override fun onTaskRemoved() {
if (!playbackManager.progression.isPlaying) {
playbackManager.playing(false)
endSession()
@@ -200,17 +189,13 @@ class PlaybackService :
}
override fun onDestroy() {
- super.onDestroy()
-
- foregroundManager.release()
-
// Pause just in case this destruction was unexpected.
playbackManager.playing(false)
playbackManager.unregisterStateHolder(this)
musicRepository.removeUpdateListener(this)
playbackSettings.unregisterListener(this)
- unregisterReceiver(systemReceiver)
+ context.unregisterReceiver(systemReceiver)
serviceJob.cancel()
widgetComponent.release()
@@ -454,7 +439,7 @@ class PlaybackService :
// Open -> Try to find the Song for the given file and then play it from all songs
is DeferredPlayback.Open -> {
logD("Opening specified file")
- deviceLibrary.findSongForUri(application, action.uri)?.let { song ->
+ deviceLibrary.findSongForUri(context.applicationContext, action.uri)?.let { song ->
playbackManager.play(
song,
null,
@@ -579,10 +564,7 @@ class PlaybackService :
// manner.
if (hasPlayed) {
logD("Played before, starting foreground state")
- if (!foregroundManager.tryStartForeground(notification)) {
- logD("Notification changed, re-posting")
- notification.post()
- }
+ startForeground(notification)
}
}
@@ -659,9 +641,9 @@ class PlaybackService :
private fun broadcastAudioEffectAction(event: String) {
logD("Broadcasting AudioEffect event: $event")
- sendBroadcast(
+ context.sendBroadcast(
Intent(event)
- .putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName)
+ .putExtra(AudioEffect.EXTRA_PACKAGE_NAME, context.packageName)
.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, audioSessionId)
.putExtra(AudioEffect.EXTRA_CONTENT_TYPE, AudioEffect.CONTENT_TYPE_MUSIC))
}
@@ -678,7 +660,7 @@ class PlaybackService :
if (!player.isPlaying) {
hasPlayed = false
playbackManager.playing(false)
- foregroundManager.tryStopForeground()
+ stopForeground()
}
}
}
diff --git a/app/src/main/java/org/oxycblt/auxio/playback/system/SystemModule.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/SystemModule.kt
similarity index 98%
rename from app/src/main/java/org/oxycblt/auxio/playback/system/SystemModule.kt
rename to app/src/main/java/org/oxycblt/auxio/playback/service/SystemModule.kt
index 47b052761..3aade8f3e 100644
--- a/app/src/main/java/org/oxycblt/auxio/playback/system/SystemModule.kt
+++ b/app/src/main/java/org/oxycblt/auxio/playback/service/SystemModule.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package org.oxycblt.auxio.playback.system
+package org.oxycblt.auxio.playback.service
import android.content.Context
import androidx.media3.datasource.ContentDataSource
diff --git a/app/src/main/java/org/oxycblt/auxio/service/AuxioService.kt b/app/src/main/java/org/oxycblt/auxio/service/AuxioService.kt
new file mode 100644
index 000000000..eb782fca4
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/service/AuxioService.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2024 Auxio Project
+ * AuxioService.kt is part of Auxio.
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.oxycblt.auxio.service
+
+import android.app.Service
+import android.content.Intent
+import androidx.core.app.ServiceCompat
+import dagger.hilt.android.AndroidEntryPoint
+import javax.inject.Inject
+import org.oxycblt.auxio.music.service.IndexerServiceFragment
+import org.oxycblt.auxio.playback.service.PlaybackServiceFragment
+
+@AndroidEntryPoint
+class AuxioService : Service() {
+ @Inject lateinit var playbackFragment: PlaybackServiceFragment
+ @Inject lateinit var indexerFragment: IndexerServiceFragment
+
+ override fun onBind(intent: Intent?) = null
+
+ override fun onCreate() {
+ super.onCreate()
+ playbackFragment.attach(this)
+ indexerFragment.attach(this)
+ }
+
+ override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
+ playbackFragment.handleIntent(intent)
+ indexerFragment.handleIntent(intent)
+ return START_NOT_STICKY
+ }
+
+ override fun onTaskRemoved(rootIntent: Intent?) {
+ super.onTaskRemoved(rootIntent)
+ playbackFragment.handleTaskRemoved()
+ indexerFragment.handleTaskRemoved()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ playbackFragment.release()
+ indexerFragment.release()
+ }
+
+ fun refreshForeground() {
+ val currentNotification = playbackFragment.notification ?: indexerFragment.notification
+ if (currentNotification != null) {
+ startForeground(currentNotification.code, currentNotification.build())
+ } else {
+ ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_DETACH)
+ }
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/service/ForegroundManager.kt b/app/src/main/java/org/oxycblt/auxio/service/ForegroundManager.kt
deleted file mode 100644
index b23457d48..000000000
--- a/app/src/main/java/org/oxycblt/auxio/service/ForegroundManager.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (c) 2022 Auxio Project
- * ForegroundManager.kt is part of Auxio.
- *
- * 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 3 of the License, or
- * (at your option) any later version.
- *
- * 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.oxycblt.auxio.service
-
-import android.app.Service
-import androidx.core.app.ServiceCompat
-import org.oxycblt.auxio.util.logD
-
-/**
- * A utility to create consistent foreground behavior for a given [Service].
- *
- * @param service [Service] to wrap in this instance.
- * @author Alexander Capehart (OxygenCobalt)
- *
- * TODO: Merge with unified service when done.
- */
-class ForegroundManager(private val service: Service) {
- private var isForeground = false
-
- /** Release this instance. */
- fun release() {
- tryStopForeground()
- }
-
- /**
- * Try to enter a foreground state.
- *
- * @param notification The [ForegroundServiceNotification] to show in order to signal the
- * foreground state.
- * @return true if the state was changed, false otherwise
- * @see Service.startForeground
- */
- fun tryStartForeground(notification: ForegroundServiceNotification): Boolean {
- if (isForeground) {
- // Nothing to do.
- return false
- }
-
- logD("Starting foreground state")
- service.startForeground(notification.code, notification.build())
- isForeground = true
- return true
- }
-
- /**
- * Try to exit a foreground state. Will remove the foreground notification.
- *
- * @return true if the state was changed, false otherwise
- * @see Service.stopForeground
- */
- fun tryStopForeground(): Boolean {
- if (!isForeground) {
- // Nothing to do.
- return false
- }
-
- logD("Stopping foreground state")
- ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE)
- isForeground = false
- return true
- }
-}
diff --git a/app/src/main/java/org/oxycblt/auxio/service/ServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/service/ServiceFragment.kt
new file mode 100644
index 000000000..b52adf629
--- /dev/null
+++ b/app/src/main/java/org/oxycblt/auxio/service/ServiceFragment.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2024 Auxio Project
+ * ServiceFragment.kt is part of Auxio.
+ *
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.oxycblt.auxio.service
+
+import android.content.Context
+import android.content.Intent
+
+abstract class ServiceFragment {
+ private var handle: AuxioService? = null
+
+ protected val context: Context
+ get() = requireNotNull(handle)
+
+ var notification: ForegroundServiceNotification? = null
+ private set
+
+ fun attach(handle: AuxioService) {
+ this.handle = handle
+ onCreate(handle)
+ }
+
+ fun release() {
+ notification = null
+ handle = null
+ onDestroy()
+ }
+
+ fun handleIntent(intent: Intent) {
+ onStartCommand(intent)
+ }
+
+ fun handleTaskRemoved() {
+ onTaskRemoved()
+ }
+
+ protected open fun onCreate(context: Context) {}
+
+ protected open fun onDestroy() {}
+
+ protected open fun onStartCommand(intent: Intent) {}
+
+ protected open fun onTaskRemoved() {}
+
+ protected fun startForeground(notification: ForegroundServiceNotification) {
+ this.notification = notification
+ requireNotNull(handle).refreshForeground()
+ }
+
+ protected fun stopForeground() {
+ this.notification = null
+ requireNotNull(handle).refreshForeground()
+ }
+}
diff --git a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
index 7a3bc6c40..3f14578f6 100644
--- a/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
+++ b/app/src/main/java/org/oxycblt/auxio/widgets/WidgetProvider.kt
@@ -32,7 +32,7 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.resolveNames
import org.oxycblt.auxio.playback.state.RepeatMode
-import org.oxycblt.auxio.playback.system.PlaybackService
+import org.oxycblt.auxio.playback.service.PlaybackServiceFragment
import org.oxycblt.auxio.ui.UISettings
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW
@@ -323,7 +323,7 @@ class WidgetProvider : AppWidgetProvider() {
// by PlaybackService.
setOnClickPendingIntent(
R.id.widget_play_pause,
- context.newBroadcastPendingIntent(PlaybackService.ACTION_PLAY_PAUSE))
+ context.newBroadcastPendingIntent(PlaybackServiceFragment.ACTION_PLAY_PAUSE))
// Set up the play/pause button appearance. Like the Android 13 media controls, use
// a circular FAB when paused, and a squircle FAB when playing. This does require us
@@ -364,10 +364,10 @@ class WidgetProvider : AppWidgetProvider() {
// by PlaybackService.
setOnClickPendingIntent(
R.id.widget_skip_prev,
- context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_PREV))
+ context.newBroadcastPendingIntent(PlaybackServiceFragment.ACTION_SKIP_PREV))
setOnClickPendingIntent(
R.id.widget_skip_next,
- context.newBroadcastPendingIntent(PlaybackService.ACTION_SKIP_NEXT))
+ context.newBroadcastPendingIntent(PlaybackServiceFragment.ACTION_SKIP_NEXT))
return this
}
@@ -389,10 +389,10 @@ class WidgetProvider : AppWidgetProvider() {
// be recognized by PlaybackService.
setOnClickPendingIntent(
R.id.widget_repeat,
- context.newBroadcastPendingIntent(PlaybackService.ACTION_INC_REPEAT_MODE))
+ context.newBroadcastPendingIntent(PlaybackServiceFragment.ACTION_INC_REPEAT_MODE))
setOnClickPendingIntent(
R.id.widget_shuffle,
- context.newBroadcastPendingIntent(PlaybackService.ACTION_INVERT_SHUFFLE))
+ context.newBroadcastPendingIntent(PlaybackServiceFragment.ACTION_INVERT_SHUFFLE))
// Set up the repeat/shuffle buttons. When working with RemoteViews, we will
// need to hard-code different accent tinting configurations, as stateful drawables
diff --git a/build.gradle b/build.gradle
index fdb033f43..eae63966b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -12,7 +12,7 @@ buildscript {
}
plugins {
- id "com.android.application" version '8.2.0' apply false
+ id "com.android.application" version '8.2.1' apply false
id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
id "com.google.devtools.ksp" version '1.9.10-1.0.13' apply false