From 30b3603cf1a9d6a6ec3ae52b1ed84158a1d4d491 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Wed, 28 Aug 2024 08:42:14 -0600 Subject: [PATCH] music: move search/notif out of service fragment Generally cleaner this way --- .../oxycblt/auxio/music/service/Indexer.kt | 42 +++++++++- .../auxio/music/service/MusicBrowser.kt | 39 +++++++++ .../music/service/MusicServiceFragment.kt | 84 ++----------------- 3 files changed, 85 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/service/Indexer.kt index 427f653ea..4618a3fb2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/Indexer.kt @@ -28,6 +28,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.ForegroundListener +import org.oxycblt.auxio.ForegroundServiceNotification import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicSettings @@ -42,7 +43,8 @@ constructor( private val playbackManager: PlaybackStateManager, private val musicRepository: MusicRepository, private val musicSettings: MusicSettings, - private val imageLoader: ImageLoader + private val imageLoader: ImageLoader, + private val contentObserver: SystemContentObserver ) : MusicRepository.IndexingWorker, MusicRepository.IndexingListener, @@ -52,6 +54,8 @@ constructor( private val indexScope = CoroutineScope(indexJob + Dispatchers.IO) private var currentIndexJob: Job? = null private var foregroundListener: ForegroundListener? = null + private val indexingNotification = IndexingNotification(workerContext) + private val observingNotification = ObservingNotification(workerContext) private val wakeLock = workerContext .getSystemServiceCompat(PowerManager::class) @@ -64,9 +68,11 @@ constructor( musicRepository.addUpdateListener(this) musicRepository.addIndexingListener(this) musicRepository.registerWorker(this) + contentObserver.attach() } fun release() { + contentObserver.release() musicSettings.registerListener(this) musicRepository.addIndexingListener(this) musicRepository.addUpdateListener(this) @@ -118,6 +124,40 @@ constructor( musicRepository.requestIndex(true) } + override fun onObservingChanged() { + super.onObservingChanged() + // Make sure we don't override the service state with the observing + // notification if we were actively loading when the automatic rescanning + // setting changed. In such a case, the state will still be updated when + // the music loading process ends. + if (musicRepository.indexingState == null) { + logD("Not loading, updating idle session") + foregroundListener?.updateForeground(ForegroundListener.Change.INDEXER) + } + } + + fun createNotification(post: (ForegroundServiceNotification?) -> Unit) { + val state = musicRepository.indexingState + if (state is IndexingState.Indexing) { + // There are a few reasons why we stay in the foreground with automatic rescanning: + // 1. Newer versions of Android have become more and more restrictive regarding + // how a foreground service starts. Thus, it's best to go foreground now so that + // we can go foreground later. + // 2. If a non-foreground service is killed, the app will probably still be alive, + // and thus the music library will not be updated at all. + val changed = indexingNotification.updateIndexingState(state.progress) + if (changed) { + post(indexingNotification) + } + } else if (musicSettings.shouldBeObserving) { + // Not observing and done loading, exit foreground. + logD("Exiting foreground") + post(observingNotification) + } else { + post(null) + } + } + /** Utility to safely acquire a [PowerManager.WakeLock] without crashes/inefficiency. */ private fun PowerManager.WakeLock.acquireSafe() { // Avoid unnecessary acquire calls. diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt index 7391632fd..eceff47d3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MusicBrowser.kt @@ -34,12 +34,14 @@ import org.oxycblt.auxio.music.Playlist import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.device.DeviceLibrary import org.oxycblt.auxio.music.user.UserLibrary +import org.oxycblt.auxio.search.SearchEngine class MusicBrowser @Inject constructor( @ApplicationContext private val context: Context, private val musicRepository: MusicRepository, + private val searchEngine: SearchEngine, private val listSettings: ListSettings ) : MusicRepository.UpdateListener { interface Invalidator { @@ -127,6 +129,43 @@ constructor( return getMediaItemList(parentId, deviceLibrary, userLibrary) } + suspend fun search(query: String): MutableList { + if (query.isEmpty()) { + return mutableListOf() + } + val deviceLibrary = + musicRepository.deviceLibrary ?: return mutableListOf() + val userLibrary = musicRepository.userLibrary ?: return mutableListOf() + val items = + SearchEngine.Items( + deviceLibrary.songs, + deviceLibrary.albums, + deviceLibrary.artists, + deviceLibrary.genres, + userLibrary.playlists) + return searchEngine.search(items, query).toMediaItems() + } + + private fun SearchEngine.Items.toMediaItems(): MutableList { + val music = mutableListOf() + if (songs != null) { + music.addAll(songs.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) }) + } + if (albums != null) { + music.addAll(albums.map { it.toMediaItem(context, null, header(R.string.lbl_albums)) }) + } + if (artists != null) { + music.addAll(artists.map { it.toMediaItem(context, header(R.string.lbl_artists)) }) + } + if (genres != null) { + music.addAll(genres.map { it.toMediaItem(context, header(R.string.lbl_genres)) }) + } + if (playlists != null) { + music.addAll(playlists.map { it.toMediaItem(context, header(R.string.lbl_playlists)) }) + } + return music + } + private fun getMediaItemList( id: String, deviceLibrary: DeviceLibrary, diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MusicServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MusicServiceFragment.kt index 80c374b0e..c1acf5144 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MusicServiceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MusicServiceFragment.kt @@ -41,18 +41,11 @@ import org.oxycblt.auxio.util.logW class MusicServiceFragment @Inject constructor( - @ApplicationContext private val context: Context, private val indexer: Indexer, private val musicBrowser: MusicBrowser, - private val musicRepository: MusicRepository, - private val musicSettings: MusicSettings, - private val searchEngine: SearchEngine, - private val contentObserver: SystemContentObserver, -) : MusicBrowser.Invalidator, MusicSettings.Listener { - private val indexingNotification = IndexingNotification(context) - private val observingNotification = ObservingNotification(context) + private val musicRepository: MusicRepository +) : MusicBrowser.Invalidator { private var invalidator: Invalidator? = null - private var foregroundListener: ForegroundListener? = null private val dispatchJob = Job() private val dispatchScope = CoroutineScope(dispatchJob + Dispatchers.Default) @@ -64,13 +57,9 @@ constructor( this.invalidator = invalidator indexer.attach(foregroundListener) musicBrowser.attach(this) - contentObserver.attach() - musicSettings.registerListener(this) } fun release() { - musicSettings.unregisterListener(this) - contentObserver.release() dispatchJob.cancel() musicBrowser.release() indexer.release() @@ -83,17 +72,6 @@ constructor( } } - override fun onObservingChanged() { - super.onObservingChanged() - // Make sure we don't override the service state with the observing - // notification if we were actively loading when the automatic rescanning - // setting changed. In such a case, the state will still be updated when - // the music loading process ends. - if (musicRepository.indexingState == null) { - logD("Not loading, updating idle session") - foregroundListener?.updateForeground(ForegroundListener.Change.INDEXER) - } - } fun start() { if (musicRepository.indexingState == null) { @@ -102,25 +80,7 @@ constructor( } fun createNotification(post: (ForegroundServiceNotification?) -> Unit) { - val state = musicRepository.indexingState - if (state is IndexingState.Indexing) { - // There are a few reasons why we stay in the foreground with automatic rescanning: - // 1. Newer versions of Android have become more and more restrictive regarding - // how a foreground service starts. Thus, it's best to go foreground now so that - // we can go foreground later. - // 2. If a non-foreground service is killed, the app will probably still be alive, - // and thus the music library will not be updated at all. - val changed = indexingNotification.updateIndexingState(state.progress) - if (changed) { - post(indexingNotification) - } - } else if (musicSettings.shouldBeObserving) { - // Not observing and done loading, exit foreground. - logD("Exiting foreground") - post(observingNotification) - } else { - post(null) - } + indexer.createNotification(post) } fun getRoot() = BrowserRoot(Category.ROOT.id, null) @@ -134,42 +94,7 @@ constructor( ) = result.dispatch { musicBrowser.getChildren(mediaId)?.toMutableList() } fun search(query: String, result: MediaBrowserServiceCompat.Result>) = - result.dispatchAsync { - if (query.isEmpty()) { - return@dispatchAsync mutableListOf() - } - val deviceLibrary = - musicRepository.deviceLibrary ?: return@dispatchAsync mutableListOf() - val userLibrary = musicRepository.userLibrary ?: return@dispatchAsync mutableListOf() - val items = - SearchEngine.Items( - deviceLibrary.songs, - deviceLibrary.albums, - deviceLibrary.artists, - deviceLibrary.genres, - userLibrary.playlists) - searchEngine.search(items, query).toMediaItems() - } - - private fun SearchEngine.Items.toMediaItems(): MutableList { - val music = mutableListOf() - if (songs != null) { - music.addAll(songs.map { it.toMediaItem(context, null, header(R.string.lbl_songs)) }) - } - if (albums != null) { - music.addAll(albums.map { it.toMediaItem(context, null, header(R.string.lbl_albums)) }) - } - if (artists != null) { - music.addAll(artists.map { it.toMediaItem(context, header(R.string.lbl_artists)) }) - } - if (genres != null) { - music.addAll(genres.map { it.toMediaItem(context, header(R.string.lbl_genres)) }) - } - if (playlists != null) { - music.addAll(playlists.map { it.toMediaItem(context, header(R.string.lbl_playlists)) }) - } - return music - } + result.dispatchAsync { musicBrowser.search(query) } private fun MediaBrowserServiceCompat.Result.dispatch(body: () -> T?) { try { @@ -187,6 +112,7 @@ constructor( private fun MediaBrowserServiceCompat.Result.dispatchAsync(body: suspend () -> T?) { dispatchScope.launch { try { + detach() val result = body() if (result == null) { logW("Result is null")