diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt index dd4c954d2..be518bfa2 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicSettings.kt @@ -48,6 +48,8 @@ interface MusicSettings : Settings { val intelligentSorting: Boolean interface Listener { + /** Called when the current music locations changed. */ + fun onMusicLocationsChanged() {} /** Called when a setting controlling how music is loaded has changed. */ fun onIndexingSettingChanged() {} /** Called when the [shouldBeObserving] configuration has changed. */ @@ -109,7 +111,10 @@ class MusicSettingsImpl @Inject constructor(@ApplicationContext private val cont // TODO: Differentiate "hard reloads" (Need the cache) and "Soft reloads" // (just need to manipulate data) when (key) { - getString(R.string.set_key_music_locations), + getString(R.string.set_key_music_locations) -> { + L.d("Dispatching music locations change") + listener.onMusicLocationsChanged() + } getString(R.string.set_key_separators), getString(R.string.set_key_auto_sort_names) -> { L.d("Dispatching indexing setting change for $key") diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt b/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt index a24918bd5..52d88c391 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/IndexingHolder.kt @@ -35,6 +35,7 @@ import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.util.getSystemServiceCompat import org.oxycblt.musikr.MusicParent +import org.oxycblt.musikr.track.UpdateTracker import timber.log.Timber as L class IndexingHolder @@ -45,7 +46,7 @@ private constructor( private val musicRepository: MusicRepository, private val musicSettings: MusicSettings, private val imageLoader: ImageLoader, - private val contentObserver: SystemContentObserver + private val updateTracker: UpdateTracker ) : MusicRepository.IndexingWorker, MusicRepository.IndexingListener, @@ -58,7 +59,7 @@ private constructor( private val musicRepository: MusicRepository, private val musicSettings: MusicSettings, private val imageLoader: ImageLoader, - private val contentObserver: SystemContentObserver + private val updateTracker: UpdateTracker ) { fun create(context: Context, listener: ForegroundListener) = IndexingHolder( @@ -68,7 +69,7 @@ private constructor( musicRepository, musicSettings, imageLoader, - contentObserver) + updateTracker) } private val indexJob = Job() @@ -87,11 +88,11 @@ private constructor( musicRepository.addUpdateListener(this) musicRepository.addIndexingListener(this) musicRepository.registerWorker(this) - contentObserver.attach() + updateTracker.track(musicSettings.musicLocations) } fun release() { - contentObserver.release() + updateTracker.release() musicRepository.unregisterWorker(this) musicRepository.removeIndexingListener(this) musicRepository.removeUpdateListener(this) @@ -163,6 +164,12 @@ private constructor( } } + override fun onMusicLocationsChanged() { + super.onMusicLocationsChanged() + updateTracker.track(musicSettings.musicLocations) + musicRepository.requestIndex(true) + } + override fun onIndexingSettingChanged() { super.onIndexingSettingChanged() musicRepository.requestIndex(true) diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt b/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt deleted file mode 100644 index a6e6f2012..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/service/SystemContentObserver.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2024 Auxio Project - * SystemContentObserver.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.music.service - -import android.content.Context -import android.database.ContentObserver -import android.os.Handler -import android.os.Looper -import android.provider.MediaStore -import dagger.hilt.android.qualifiers.ApplicationContext -import javax.inject.Inject -import org.oxycblt.auxio.music.MusicRepository -import org.oxycblt.auxio.music.MusicSettings -import timber.log.Timber as L - -/** - * A [ContentObserver] that observes the [MediaStore] music database for changes, a behavior known - * to the user as automatic rescanning. The active (and not passive) nature of observing the - * database is what requires [MusicServiceFragment] to stay foreground when this is enabled. - */ -class SystemContentObserver -@Inject -constructor( - @ApplicationContext private val context: Context, - private val musicRepository: MusicRepository, - private val musicSettings: MusicSettings -) : ContentObserver(Handler(Looper.getMainLooper())), Runnable { - private val handler = Handler(Looper.getMainLooper()) - - fun attach() { - context.applicationContext.contentResolver.registerContentObserver( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this) - } - - /** - * Release this instance, preventing it from further observing the database and cancelling any - * pending update events. - */ - fun release() { - handler.removeCallbacks(this) - context.applicationContext.contentResolver.unregisterContentObserver(this) - } - - override fun onChange(selfChange: Boolean) { - // Batch rapid-fire updates to the library into a single call to run after 500ms - handler.removeCallbacks(this) - handler.postDelayed(this, REINDEX_DELAY_MS) - } - - override fun run() { - // Check here if we should even start a reindex. This is much less bug-prone than - // registering and de-registering this component as this setting changes. - if (musicSettings.shouldBeObserving) { - L.d("MediaStore changed, starting re-index") - musicRepository.requestIndex(true) - } - } - - private companion object { - const val REINDEX_DELAY_MS = 500L - } -} diff --git a/musikr/src/main/java/org/oxycblt/musikr/track/LocationObserver.kt b/musikr/src/main/java/org/oxycblt/musikr/track/LocationObserver.kt new file mode 100644 index 000000000..fca57c4aa --- /dev/null +++ b/musikr/src/main/java/org/oxycblt/musikr/track/LocationObserver.kt @@ -0,0 +1,42 @@ +package org.oxycblt.musikr.track + +import android.content.Context +import android.database.ContentObserver +import android.os.Handler +import android.os.Looper +import org.oxycblt.musikr.fs.MusicLocation + +internal class LocationObserver( + private val context: Context, + private val location: MusicLocation, + private val listener: UpdateTracker.Callback +) : ContentObserver(Handler(Looper.getMainLooper())), Runnable { + private val handler = Handler(Looper.getMainLooper()) + + init { + context.applicationContext.contentResolver.registerContentObserver( + location.uri, + true, + this + ) + } + + fun release() { + handler.removeCallbacks(this) + context.applicationContext.contentResolver.unregisterContentObserver(this) + } + + override fun onChange(selfChange: Boolean) { + // Batch rapid-fire updates into a single callback after delay + handler.removeCallbacks(this) + handler.postDelayed(this, REINDEX_DELAY_MS) + } + + override fun run() { + listener.onUpdate(location) + } + + private companion object { + const val REINDEX_DELAY_MS = 500L + } +} \ No newline at end of file diff --git a/musikr/src/main/java/org/oxycblt/musikr/track/UpdateTracker.kt b/musikr/src/main/java/org/oxycblt/musikr/track/UpdateTracker.kt new file mode 100644 index 000000000..1e1c3db68 --- /dev/null +++ b/musikr/src/main/java/org/oxycblt/musikr/track/UpdateTracker.kt @@ -0,0 +1,35 @@ +package org.oxycblt.musikr.track + +import android.content.Context +import org.oxycblt.musikr.fs.MusicLocation + +interface UpdateTracker { + fun track(locations: List) + + fun release() + + interface Callback { + fun onUpdate(location: MusicLocation) + } + + companion object { + fun from(context: Context, callback: Callback): UpdateTracker = UpdateTrackerImpl(context, callback) + } +} + +private class UpdateTrackerImpl( + private val context: Context, + private val callback: UpdateTracker.Callback +) : UpdateTracker { + private val observers = mutableListOf() + + override fun track(locations: List) { + release() + observers.addAll(locations.map { LocationObserver(context, it, callback) }) + } + + override fun release() { + observers.forEach { it.release() } + observers.clear() + } +} \ No newline at end of file