diff --git a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt index ebc0c18e9..2970305cb 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/BitmapProvider.kt @@ -49,10 +49,11 @@ class BitmapProvider(private val context: Context) { * Load a bitmap from [song]. [target] should be a new object, not a reference to an existing * callback. */ + @Synchronized fun load(song: Song, target: Target) { // Increment the generation value so that previous requests are invalidated. // This is a second safeguard to mitigate instruction-by-instruction race conditions. - val generation = synchronized(this) { ++currentGeneration } + val generation = ++currentGeneration currentRequest?.run { disposable.dispose() } currentRequest = null diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 5c8cac7f1..01d1e484e 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -73,6 +73,7 @@ class Indexer { get() = indexingState != null /** Register a [Controller] with this instance. */ + @Synchronized fun registerController(controller: Controller) { if (BuildConfig.DEBUG && this.controller != null) { logW("Controller is already registered") @@ -83,15 +84,17 @@ class Indexer { } /** Unregister a [Controller] with this instance. */ + @Synchronized fun unregisterController(controller: Controller) { if (BuildConfig.DEBUG && this.controller !== controller) { logW("Given controller did not match current controller") return } - synchronized(this) { this.controller = null } + this.controller = null } + @Synchronized fun registerCallback(callback: Callback) { if (BuildConfig.DEBUG && this.callback != null) { logW("Callback is already registered") @@ -106,6 +109,7 @@ class Indexer { this.callback = callback } + @Synchronized fun unregisterCallback(callback: Callback) { if (BuildConfig.DEBUG && this.callback !== callback) { logW("Given controller did not match current controller") @@ -115,8 +119,9 @@ class Indexer { this.callback = null } + @Synchronized fun index(context: Context) { - val generation = synchronized(this) { ++currentGeneration } + val generation = ++currentGeneration val notGranted = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == @@ -153,6 +158,7 @@ class Indexer { * Request that re-indexing should be done. This should be used by components that do not manage * the indexing process to re-index music. */ + @Synchronized fun requestReindex() { logD("Requesting reindex") controller?.onStartIndexing() @@ -164,46 +170,42 @@ class Indexer { * canceled, in which it would be useful for any ongoing loading process to not accidentally * corrupt the current state. */ + @Synchronized fun cancelLast() { logD("Cancelling last job") - synchronized(this) { - currentGeneration++ - emitIndexing(null, currentGeneration) - } + currentGeneration++ + emitIndexing(null, currentGeneration) } + @Synchronized private fun emitIndexing(indexing: Indexing?, generation: Long) { - synchronized(this) { - if (currentGeneration != generation) { - return - } - - indexingState = indexing - - // If we have canceled the loading process, we want to revert to a previous completion - // whenever possible to prevent state inconsistency. - val state = - indexingState?.let { State.Indexing(it) } - ?: lastResponse?.let { State.Complete(it) } - - controller?.onIndexerStateChanged(state) - callback?.onIndexerStateChanged(state) + if (currentGeneration != generation) { + return } + + indexingState = indexing + + // If we have canceled the loading process, we want to revert to a previous completion + // whenever possible to prevent state inconsistency. + val state = + indexingState?.let { State.Indexing(it) } ?: lastResponse?.let { State.Complete(it) } + + controller?.onIndexerStateChanged(state) + callback?.onIndexerStateChanged(state) } + @Synchronized private fun emitCompletion(response: Response, generation: Long) { - synchronized(this) { - if (currentGeneration != generation) { - return - } - - lastResponse = response - indexingState = null - - val state = State.Complete(response) - controller?.onIndexerStateChanged(state) - callback?.onIndexerStateChanged(state) + if (currentGeneration != generation) { + return } + + lastResponse = response + indexingState = null + + val state = State.Complete(response) + controller?.onIndexerStateChanged(state) + callback?.onIndexerStateChanged(state) } /** diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index 4122372af..644b78830 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -46,12 +46,12 @@ class MusicStore private constructor() { callbacks.remove(callback) } + /** Update the library in this instance. This is only meant for use by the internal indexer. */ + @Synchronized fun updateLibrary(newLibrary: Library?) { - synchronized(this) { - library = newLibrary - for (callback in callbacks) { - callback.onLibraryChanged(library) - } + library = newLibrary + for (callback in callbacks) { + callback.onLibraryChanged(library) } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt index 04f12b4e4..cd934fe4f 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateDatabase.kt @@ -101,6 +101,7 @@ class PlaybackStateDatabase(context: Context) : // --- INTERFACE FUNCTIONS --- + @Synchronized fun read(library: MusicStore.Library): SavedState? { requireBackgroundThread() @@ -185,38 +186,37 @@ class PlaybackStateDatabase(context: Context) : } /** Clear the previously written [SavedState] and write a new one. */ + @Synchronized fun write(state: SavedState) { requireBackgroundThread() - synchronized(this) { - val song = state.queue.getOrNull(state.index) + val song = state.queue.getOrNull(state.index) - if (song != null) { - val rawState = - RawState( - index = state.index, - positionMs = state.positionMs, - repeatMode = state.repeatMode, - isShuffled = state.isShuffled, - songId = song.id, - parentId = state.parent?.id, - playbackMode = - when (state.parent) { - null -> PlaybackMode.ALL_SONGS - is Album -> PlaybackMode.IN_ALBUM - is Artist -> PlaybackMode.IN_ARTIST - is Genre -> PlaybackMode.IN_GENRE - }) + if (song != null) { + val rawState = + RawState( + index = state.index, + positionMs = state.positionMs, + repeatMode = state.repeatMode, + isShuffled = state.isShuffled, + songId = song.id, + parentId = state.parent?.id, + playbackMode = + when (state.parent) { + null -> PlaybackMode.ALL_SONGS + is Album -> PlaybackMode.IN_ALBUM + is Artist -> PlaybackMode.IN_ARTIST + is Genre -> PlaybackMode.IN_GENRE + }) - writeRawState(rawState) - writeQueue(state.queue) - } else { - writeRawState(null) - writeQueue(null) - } - - logD("Wrote state to database") + writeRawState(rawState) + writeQueue(state.queue) + } else { + writeRawState(null) + writeQueue(null) } + + logD("Wrote state to database") } private fun writeRawState(rawState: RawState?) { 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 accc292bf..451ae8d2a 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 @@ -45,8 +45,6 @@ import org.oxycblt.auxio.util.logW * * All access should be done with [PlaybackStateManager.getInstance]. * @author OxygenCobalt - * - * TODO: Replace synchronized calls with annotation */ class PlaybackStateManager private constructor() { private val musicStore = MusicStore.getInstance() @@ -93,45 +91,45 @@ class PlaybackStateManager private constructor() { private var controller: Controller? = null /** Add a callback to this instance. Make sure to remove it when done. */ + @Synchronized fun addCallback(callback: Callback) { - synchronized(this) { - if (isInitialized) { - callback.onNewPlayback(index, queue, parent) - callback.onPositionChanged(positionMs) - callback.onPlayingChanged(isPlaying) - callback.onRepeatChanged(repeatMode) - callback.onShuffledChanged(isShuffled) - } - - callbacks.add(callback) + if (isInitialized) { + callback.onNewPlayback(index, queue, parent) + callback.onPositionChanged(positionMs) + callback.onPlayingChanged(isPlaying) + callback.onRepeatChanged(repeatMode) + callback.onShuffledChanged(isShuffled) } + + callbacks.add(callback) } /** Remove a [Callback] bound to this instance. */ + @Synchronized fun removeCallback(callback: Callback) { callbacks.remove(callback) } /** Register a [Controller] with this instance. */ + @Synchronized fun registerController(controller: Controller) { if (BuildConfig.DEBUG && this.controller != null) { logW("Controller is already registered") return } - synchronized(this) { - if (isInitialized) { - controller.loadSong(song) - controller.seekTo(positionMs) - controller.onPlayingChanged(isPlaying) - controller.onPlayingChanged(isPlaying) - } - - this.controller = controller + if (isInitialized) { + controller.loadSong(song) + controller.seekTo(positionMs) + controller.onPlayingChanged(isPlaying) + controller.onPlayingChanged(isPlaying) } + + this.controller = controller } /** Unregister a [Controller] with this instance. */ + @Synchronized fun unregisterController(controller: Controller) { if (BuildConfig.DEBUG && this.controller !== controller) { logW("Given controller did not match current controller") @@ -147,84 +145,78 @@ class PlaybackStateManager private constructor() { * Play a [song]. * @param playbackMode The [PlaybackMode] to construct the queue off of. */ + @Synchronized fun play(song: Song, playbackMode: PlaybackMode, settings: Settings) { val library = musicStore.library ?: return - synchronized(this) { - parent = - when (playbackMode) { - PlaybackMode.ALL_SONGS -> null - PlaybackMode.IN_ALBUM -> song.album - PlaybackMode.IN_ARTIST -> song.album.artist - PlaybackMode.IN_GENRE -> song.genre - } + parent = + when (playbackMode) { + PlaybackMode.ALL_SONGS -> null + PlaybackMode.IN_ALBUM -> song.album + PlaybackMode.IN_ARTIST -> song.album.artist + PlaybackMode.IN_GENRE -> song.genre + } - applyNewQueue(library, settings, settings.keepShuffle && isShuffled, song) - seekTo(0) - notifyNewPlayback() - notifyShuffledChanged() - isPlaying = true - isInitialized = true - } + applyNewQueue(library, settings, settings.keepShuffle && isShuffled, song) + seekTo(0) + notifyNewPlayback() + notifyShuffledChanged() + isPlaying = true + isInitialized = true } /** * Play a [parent], such as an artist or album. * @param shuffled Whether the queue is shuffled or not */ + @Synchronized fun play(parent: MusicParent, shuffled: Boolean, settings: Settings) { val library = musicStore.library ?: return - - synchronized(this) { - this.parent = parent - applyNewQueue(library, settings, shuffled, null) - seekTo(0) - notifyNewPlayback() - notifyShuffledChanged() - isPlaying = true - isInitialized = true - } + this.parent = parent + applyNewQueue(library, settings, shuffled, null) + seekTo(0) + notifyNewPlayback() + notifyShuffledChanged() + isPlaying = true + isInitialized = true } /** Shuffle all songs. */ + @Synchronized fun shuffleAll(settings: Settings) { val library = musicStore.library ?: return - synchronized(this) { - parent = null - applyNewQueue(library, settings, true, null) - seekTo(0) - notifyNewPlayback() - notifyShuffledChanged() - isPlaying = true - isInitialized = true - } + parent = null + applyNewQueue(library, settings, true, null) + seekTo(0) + notifyNewPlayback() + notifyShuffledChanged() + isPlaying = true + isInitialized = true } // --- QUEUE FUNCTIONS --- /** Go to the next song, along with doing all the checks that entails. */ + @Synchronized fun next() { - synchronized(this) { - // Increment the index, if it cannot be incremented any further, then - // repeat and pause/resume playback depending on the setting - if (index < _queue.lastIndex) { - goto(index + 1, true) - } else { - goto(0, repeatMode == RepeatMode.ALL) - } + // Increment the index, if it cannot be incremented any further, then + // repeat and pause/resume playback depending on the setting + if (index < _queue.lastIndex) { + goto(index + 1, true) + } else { + goto(0, repeatMode == RepeatMode.ALL) } } /** Go to the previous song, doing any checks that are needed. */ + @Synchronized fun prev() { - synchronized(this) { - // If enabled, rewind before skipping back if the position is past 3 seconds [3000ms] - if (controller?.shouldPrevRewind() == true) { - rewind() - isPlaying = true - } else { - goto(max(index - 1, 0), true) - } + // If enabled, rewind before skipping back if the position is past 3 seconds [3000ms] + if (controller?.shouldPrevRewind() == true) { + rewind() + isPlaying = true + } else { + goto(max(index - 1, 0), true) } } @@ -236,66 +228,57 @@ class PlaybackStateManager private constructor() { } /** Add a [song] to the top of the queue. */ + @Synchronized fun playNext(song: Song) { - synchronized(this) { - _queue.add(index + 1, song) - notifyQueueChanged() - } + _queue.add(index + 1, song) + notifyQueueChanged() } /** Add a list of [songs] to the top of the queue. */ + @Synchronized fun playNext(songs: List) { - synchronized(this) { - _queue.addAll(index + 1, songs) - notifyQueueChanged() - } + _queue.addAll(index + 1, songs) + notifyQueueChanged() } /** Add a [song] to the end of the queue. */ + @Synchronized fun addToQueue(song: Song) { - synchronized(this) { - _queue.add(song) - notifyQueueChanged() - } + _queue.add(song) + notifyQueueChanged() } /** Add a list of [songs] to the end of the queue. */ + @Synchronized fun addToQueue(songs: List) { - synchronized(this) { - _queue.addAll(songs) - notifyQueueChanged() - } + _queue.addAll(songs) + notifyQueueChanged() } /** Move a queue item at [from] to a position at [to]. Will ignore invalid indexes. */ + @Synchronized fun moveQueueItem(from: Int, to: Int) { logD("Moving item $from to position $to") - - synchronized(this) { - _queue.add(to, _queue.removeAt(from)) - notifyQueueChanged() - } + _queue.add(to, _queue.removeAt(from)) + notifyQueueChanged() } /** Remove a queue item at [index]. Will ignore invalid indexes. */ + @Synchronized fun removeQueueItem(index: Int) { logD("Removing item ${_queue[index].rawName}") - - synchronized(this) { - _queue.removeAt(index) - notifyQueueChanged() - } + _queue.removeAt(index) + notifyQueueChanged() } /** Set whether this instance is [shuffled]. Updates the queue accordingly. */ + @Synchronized fun reshuffle(shuffled: Boolean, settings: Settings) { val library = musicStore.library ?: return - synchronized(this) { - val song = song ?: return - applyNewQueue(library, settings, shuffled, song) - notifyQueueChanged() - notifyShuffledChanged() - } + val song = song ?: return + applyNewQueue(library, settings, shuffled, song) + notifyQueueChanged() + notifyShuffledChanged() } private fun applyNewQueue( @@ -343,19 +326,18 @@ class PlaybackStateManager private constructor() { * @param positionMs The new position in millis. * @see seekTo */ + @Synchronized fun synchronizePosition(controller: Controller, positionMs: Long) { if (BuildConfig.DEBUG && this.controller !== controller) { logW("Given controller did not match current controller") return } - synchronized(this) { - // Don't accept any bugged positions that are over the duration of the song. - val maxDuration = song?.durationMs ?: -1 - if (positionMs <= maxDuration) { - this.positionMs = positionMs - notifyPositionChanged() - } + // Don't accept any bugged positions that are over the duration of the song. + val maxDuration = song?.durationMs ?: -1 + if (positionMs <= maxDuration) { + this.positionMs = positionMs + notifyPositionChanged() } } @@ -410,8 +392,8 @@ class PlaybackStateManager private constructor() { ): PlaybackStateDatabase.SavedState? { logD("Getting state from DB") - val start: Long = System.currentTimeMillis() - val state: PlaybackStateDatabase.SavedState? = database.read(library) + val start = System.currentTimeMillis() + val state = database.read(library) logD("State read completed successfully in ${System.currentTimeMillis() - start}ms") diff --git a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt index 51bbe08d0..2b514358c 100644 --- a/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt +++ b/app/src/main/java/org/oxycblt/auxio/util/ContextUtil.kt @@ -40,7 +40,6 @@ import androidx.annotation.Px import androidx.annotation.StringRes import androidx.core.content.ContextCompat import kotlin.reflect.KClass -import kotlin.system.exitProcess import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.MainActivity @@ -236,4 +235,3 @@ fun Context.newBroadcastPendingIntent(what: String): PendingIntent = IntegerTable.REQUEST_CODE, Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY), if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) -