music: enable runtime rescanning [#72]
Finally enable runtime rescanning, opening the door for a ton of new features and automatic rescanning later on. More work needs to be done on making the shared mutable state in-app safer to use.
This commit is contained in:
parent
bbf3b1778b
commit
73e10c9519
8 changed files with 104 additions and 75 deletions
|
@ -68,6 +68,10 @@ class Indexer {
|
||||||
val isIndeterminate: Boolean
|
val isIndeterminate: Boolean
|
||||||
get() = lastResponse == null && indexingState == null
|
get() = lastResponse == null && indexingState == null
|
||||||
|
|
||||||
|
/** Whether this instance is actively indexing or not. */
|
||||||
|
val isIndexing: Boolean
|
||||||
|
get() = indexingState != null
|
||||||
|
|
||||||
/** Register a [Controller] with this instance. */
|
/** Register a [Controller] with this instance. */
|
||||||
fun registerController(controller: Controller) {
|
fun registerController(controller: Controller) {
|
||||||
if (BuildConfig.DEBUG && this.controller != null) {
|
if (BuildConfig.DEBUG && this.controller != null) {
|
||||||
|
|
|
@ -21,12 +21,16 @@ import android.app.Service
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
|
import coil.imageLoader
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.oxycblt.auxio.IntegerTable
|
import org.oxycblt.auxio.IntegerTable
|
||||||
|
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
|
||||||
|
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,7 +50,9 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
|
|
||||||
private val serviceJob = Job()
|
private val serviceJob = Job()
|
||||||
private val indexScope = CoroutineScope(serviceJob + Dispatchers.IO)
|
private val indexScope = CoroutineScope(serviceJob + Dispatchers.IO)
|
||||||
private val updateScope = CoroutineScope(serviceJob + Dispatchers.IO)
|
private val updateScope = CoroutineScope(serviceJob + Dispatchers.Main)
|
||||||
|
|
||||||
|
private val playbackManager = PlaybackStateManager.getInstance()
|
||||||
|
|
||||||
private var isForeground = false
|
private var isForeground = false
|
||||||
private lateinit var notification: IndexerNotification
|
private lateinit var notification: IndexerNotification
|
||||||
|
@ -80,6 +86,11 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartIndexing() {
|
override fun onStartIndexing() {
|
||||||
|
if (indexer.isIndexing) {
|
||||||
|
indexer.cancelLast()
|
||||||
|
indexScope.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
indexScope.launch { indexer.index(this@IndexerService) }
|
indexScope.launch { indexer.index(this@IndexerService) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,16 +101,23 @@ class IndexerService : Service(), Indexer.Controller {
|
||||||
state.response.library != musicStore.library) {
|
state.response.library != musicStore.library) {
|
||||||
logD("Applying new library")
|
logD("Applying new library")
|
||||||
|
|
||||||
|
val newLibrary = state.response.library
|
||||||
|
|
||||||
// Load was completed successfully, so apply the new library if we
|
// Load was completed successfully, so apply the new library if we
|
||||||
// have not already. Only when we are done updating the library will
|
// have not already. Only when we are done updating the library will
|
||||||
// the service stop it's foreground state.
|
// the service stop it's foreground state.
|
||||||
updateScope.launch {
|
updateScope.launch {
|
||||||
// TODO: Update PlaybackStateManager here
|
imageLoader.memoryCache?.clear()
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
if (musicStore.library != null) {
|
||||||
musicStore.updateLibrary(state.response.library)
|
// This is a new library, so we need to make sure the playback state
|
||||||
|
// is coherent.
|
||||||
|
playbackManager.sanitize(
|
||||||
|
PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) { musicStore.updateLibrary(newLibrary) }
|
||||||
|
|
||||||
stopForegroundSession()
|
stopForegroundSession()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -83,6 +83,7 @@ class MusicStore private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sanitize(song: Song) = songs.find { it.id == song.id }
|
fun sanitize(song: Song) = songs.find { it.id == song.id }
|
||||||
|
fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) }
|
||||||
fun sanitize(album: Album) = albums.find { it.id == album.id }
|
fun sanitize(album: Album) = albums.find { it.id == album.id }
|
||||||
fun sanitize(artist: Artist) = artists.find { it.id == artist.id }
|
fun sanitize(artist: Artist) = artists.find { it.id == artist.id }
|
||||||
fun sanitize(genre: Genre) = genres.find { it.id == genre.id }
|
fun sanitize(genre: Genre) = genres.find { it.id == genre.id }
|
||||||
|
|
|
@ -25,17 +25,18 @@ import android.view.LayoutInflater
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import org.oxycblt.auxio.BuildConfig
|
import org.oxycblt.auxio.BuildConfig
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
import org.oxycblt.auxio.databinding.DialogMusicDirsBinding
|
||||||
import org.oxycblt.auxio.music.Directory
|
import org.oxycblt.auxio.music.Directory
|
||||||
|
import org.oxycblt.auxio.music.IndexerViewModel
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.settings.Settings
|
import org.oxycblt.auxio.settings.Settings
|
||||||
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.context
|
import org.oxycblt.auxio.util.context
|
||||||
import org.oxycblt.auxio.util.getSystemServiceSafe
|
import org.oxycblt.auxio.util.getSystemServiceSafe
|
||||||
import org.oxycblt.auxio.util.hardRestart
|
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.showToast
|
import org.oxycblt.auxio.util.showToast
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ import org.oxycblt.auxio.util.showToast
|
||||||
class MusicDirsDialog :
|
class MusicDirsDialog :
|
||||||
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
ViewBindingDialogFragment<DialogMusicDirsBinding>(), MusicDirAdapter.Listener {
|
||||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||||
|
private val indexerModel: IndexerViewModel by activityViewModels()
|
||||||
|
|
||||||
private val dirAdapter = MusicDirAdapter(this)
|
private val dirAdapter = MusicDirAdapter(this)
|
||||||
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) }
|
||||||
|
@ -85,7 +87,7 @@ class MusicDirsDialog :
|
||||||
if (dirs.dirs != dirAdapter.data.currentList ||
|
if (dirs.dirs != dirAdapter.data.currentList ||
|
||||||
dirs.shouldInclude != isInclude(requireBinding())) {
|
dirs.shouldInclude != isInclude(requireBinding())) {
|
||||||
logD("Committing changes")
|
logD("Committing changes")
|
||||||
saveAndRestart()
|
saveAndDismiss()
|
||||||
} else {
|
} else {
|
||||||
logD("Dropping changes")
|
logD("Dropping changes")
|
||||||
dismiss()
|
dismiss()
|
||||||
|
@ -186,10 +188,10 @@ class MusicDirsDialog :
|
||||||
private fun isInclude(binding: DialogMusicDirsBinding) =
|
private fun isInclude(binding: DialogMusicDirsBinding) =
|
||||||
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include
|
||||||
|
|
||||||
private fun saveAndRestart() {
|
private fun saveAndDismiss() {
|
||||||
settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
|
settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding())))
|
||||||
|
indexerModel.reindex()
|
||||||
playbackModel.savePlaybackState(requireContext()) { requireContext().hardRestart() }
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun requireStorageManager(): StorageManager {
|
private fun requireStorageManager(): StorageManager {
|
||||||
|
|
|
@ -45,6 +45,8 @@ import org.oxycblt.auxio.util.logW
|
||||||
*
|
*
|
||||||
* All access should be done with [PlaybackStateManager.getInstance].
|
* All access should be done with [PlaybackStateManager.getInstance].
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
|
*
|
||||||
|
* TODO: Replace synchronized calls with annotation
|
||||||
*/
|
*/
|
||||||
class PlaybackStateManager private constructor() {
|
class PlaybackStateManager private constructor() {
|
||||||
private val musicStore = MusicStore.getInstance()
|
private val musicStore = MusicStore.getInstance()
|
||||||
|
@ -361,12 +363,11 @@ class PlaybackStateManager private constructor() {
|
||||||
* **Seek** to a [positionMs].
|
* **Seek** to a [positionMs].
|
||||||
* @param positionMs The position to seek to in millis.
|
* @param positionMs The position to seek to in millis.
|
||||||
*/
|
*/
|
||||||
|
@Synchronized
|
||||||
fun seekTo(positionMs: Long) {
|
fun seekTo(positionMs: Long) {
|
||||||
synchronized(this) {
|
this.positionMs = positionMs
|
||||||
this.positionMs = positionMs
|
controller?.seekTo(positionMs)
|
||||||
controller?.seekTo(positionMs)
|
notifyPositionChanged()
|
||||||
notifyPositionChanged()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rewind to the beginning of a song. */
|
/** Rewind to the beginning of a song. */
|
||||||
|
@ -377,34 +378,8 @@ class PlaybackStateManager private constructor() {
|
||||||
/** Restore the state from the [database] */
|
/** Restore the state from the [database] */
|
||||||
suspend fun restoreState(database: PlaybackStateDatabase) {
|
suspend fun restoreState(database: PlaybackStateDatabase) {
|
||||||
val library = musicStore.library ?: return
|
val library = musicStore.library ?: return
|
||||||
val start: Long
|
withContext(Dispatchers.IO) { readImpl(database, library) }?.let(::restoreImpl)
|
||||||
val state: PlaybackStateDatabase.SavedState?
|
isInitialized = true
|
||||||
|
|
||||||
logD("Getting state from DB")
|
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
start = System.currentTimeMillis()
|
|
||||||
state = database.read(library)
|
|
||||||
}
|
|
||||||
|
|
||||||
logD("State read completed successfully in ${System.currentTimeMillis() - start}ms")
|
|
||||||
|
|
||||||
synchronized(this) {
|
|
||||||
if (state != null) {
|
|
||||||
index = state.index
|
|
||||||
parent = state.parent
|
|
||||||
_queue = state.queue.toMutableList()
|
|
||||||
repeatMode = state.repeatMode
|
|
||||||
isShuffled = state.isShuffled
|
|
||||||
|
|
||||||
notifyNewPlayback()
|
|
||||||
seekTo(state.positionMs)
|
|
||||||
notifyRepeatModeChanged()
|
|
||||||
notifyShuffledChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
isInitialized = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Save the current state to the [database]. */
|
/** Save the current state to the [database]. */
|
||||||
|
@ -412,23 +387,66 @@ class PlaybackStateManager private constructor() {
|
||||||
logD("Saving state to DB")
|
logD("Saving state to DB")
|
||||||
|
|
||||||
// Pack the entire state and save it to the database.
|
// Pack the entire state and save it to the database.
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) { saveImpl(database) }
|
||||||
val start = System.currentTimeMillis()
|
}
|
||||||
|
|
||||||
database.write(
|
suspend fun sanitize(database: PlaybackStateDatabase, newLibrary: MusicStore.Library) {
|
||||||
synchronized(this) {
|
// Since we need to sanitize the state and re-save it for consistency, take the
|
||||||
PlaybackStateDatabase.SavedState(
|
// easy way out and just write a new state and restore from it. Don't really care.
|
||||||
index = index,
|
logD("Sanitizing state")
|
||||||
parent = parent,
|
isPlaying = false
|
||||||
queue = _queue,
|
val state =
|
||||||
positionMs = positionMs,
|
withContext(Dispatchers.IO) {
|
||||||
isShuffled = isShuffled,
|
saveImpl(database)
|
||||||
repeatMode = repeatMode)
|
readImpl(database, newLibrary)
|
||||||
})
|
}
|
||||||
|
|
||||||
this@PlaybackStateManager.logD(
|
state?.let(::restoreImpl)
|
||||||
"State save completed successfully in ${System.currentTimeMillis() - start}ms")
|
}
|
||||||
}
|
|
||||||
|
private fun readImpl(
|
||||||
|
database: PlaybackStateDatabase,
|
||||||
|
library: MusicStore.Library
|
||||||
|
): PlaybackStateDatabase.SavedState? {
|
||||||
|
logD("Getting state from DB")
|
||||||
|
|
||||||
|
val start: Long = System.currentTimeMillis()
|
||||||
|
val state: PlaybackStateDatabase.SavedState? = database.read(library)
|
||||||
|
|
||||||
|
logD("State read completed successfully in ${System.currentTimeMillis() - start}ms")
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun restoreImpl(state: PlaybackStateDatabase.SavedState) {
|
||||||
|
index = state.index
|
||||||
|
parent = state.parent
|
||||||
|
_queue = state.queue.toMutableList()
|
||||||
|
repeatMode = state.repeatMode
|
||||||
|
isShuffled = state.isShuffled
|
||||||
|
|
||||||
|
notifyNewPlayback()
|
||||||
|
seekTo(state.positionMs)
|
||||||
|
notifyRepeatModeChanged()
|
||||||
|
notifyShuffledChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
private fun saveImpl(database: PlaybackStateDatabase) {
|
||||||
|
val start = System.currentTimeMillis()
|
||||||
|
|
||||||
|
database.write(
|
||||||
|
PlaybackStateDatabase.SavedState(
|
||||||
|
index = index,
|
||||||
|
parent = parent,
|
||||||
|
queue = _queue,
|
||||||
|
positionMs = positionMs,
|
||||||
|
isShuffled = isShuffled,
|
||||||
|
repeatMode = repeatMode))
|
||||||
|
|
||||||
|
this@PlaybackStateManager.logD(
|
||||||
|
"State save completed successfully in ${System.currentTimeMillis() - start}ms")
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- CALLBACKS ---
|
// --- CALLBACKS ---
|
||||||
|
|
|
@ -24,7 +24,6 @@ import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import com.google.android.exoplayer2.C
|
import com.google.android.exoplayer2.C
|
||||||
import com.google.android.exoplayer2.ExoPlayer
|
import com.google.android.exoplayer2.ExoPlayer
|
||||||
|
@ -273,10 +272,6 @@ class PlaybackService :
|
||||||
|
|
||||||
override fun onPostNotification(notification: NotificationComponent) {
|
override fun onPostNotification(notification: NotificationComponent) {
|
||||||
if (hasPlayed && playbackManager.song != null) {
|
if (hasPlayed && playbackManager.song != null) {
|
||||||
logD(
|
|
||||||
mediaSessionComponent.mediaSession.controller.metadata.getText(
|
|
||||||
MediaMetadataCompat.METADATA_KEY_TITLE))
|
|
||||||
|
|
||||||
if (!isForeground) {
|
if (!isForeground) {
|
||||||
startForeground(IntegerTable.PLAYBACK_NOTIFICATION_CODE, notification.build())
|
startForeground(IntegerTable.PLAYBACK_NOTIFICATION_CODE, notification.build())
|
||||||
isForeground = true
|
isForeground = true
|
||||||
|
|
|
@ -22,6 +22,7 @@ import android.view.View
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
|
@ -30,6 +31,7 @@ import androidx.recyclerview.widget.RecyclerView
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import org.oxycblt.auxio.R
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
import org.oxycblt.auxio.home.tabs.TabCustomizeDialog
|
||||||
|
import org.oxycblt.auxio.music.IndexerViewModel
|
||||||
import org.oxycblt.auxio.music.dirs.MusicDirsDialog
|
import org.oxycblt.auxio.music.dirs.MusicDirsDialog
|
||||||
import org.oxycblt.auxio.playback.PlaybackViewModel
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
||||||
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
|
import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog
|
||||||
|
@ -38,7 +40,6 @@ import org.oxycblt.auxio.settings.ui.IntListPreferenceDialog
|
||||||
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
|
import org.oxycblt.auxio.settings.ui.WrappedDialogPreference
|
||||||
import org.oxycblt.auxio.ui.accent.AccentCustomizeDialog
|
import org.oxycblt.auxio.ui.accent.AccentCustomizeDialog
|
||||||
import org.oxycblt.auxio.util.androidActivityViewModels
|
import org.oxycblt.auxio.util.androidActivityViewModels
|
||||||
import org.oxycblt.auxio.util.hardRestart
|
|
||||||
import org.oxycblt.auxio.util.isNight
|
import org.oxycblt.auxio.util.isNight
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logEOrThrow
|
import org.oxycblt.auxio.util.logEOrThrow
|
||||||
|
@ -56,6 +57,7 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
||||||
@Suppress("UNUSED")
|
@Suppress("UNUSED")
|
||||||
class SettingsListFragment : PreferenceFragmentCompat() {
|
class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
private val playbackModel: PlaybackViewModel by androidActivityViewModels()
|
||||||
|
private val indexerModel: IndexerViewModel by activityViewModels()
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
@ -123,7 +125,7 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getString(R.string.set_key_reindex) -> {
|
getString(R.string.set_key_reindex) -> {
|
||||||
playbackModel.savePlaybackState(requireContext()) { context?.hardRestart() }
|
indexerModel.reindex()
|
||||||
}
|
}
|
||||||
else -> return super.onPreferenceTreeClick(preference)
|
else -> return super.onPreferenceTreeClick(preference)
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,14 +237,3 @@ fun Context.newBroadcastPendingIntent(what: String): PendingIntent =
|
||||||
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
Intent(what).setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY),
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0)
|
||||||
|
|
||||||
/** Hard-restarts the app. Useful for forcing the app to reload music. */
|
|
||||||
fun Context.hardRestart() {
|
|
||||||
// Instead of having to do a ton of cleanup and horrible code changes
|
|
||||||
// to restart this application non-destructively, I just restart the UI task [There is only
|
|
||||||
// one, after all] and then kill the application using exitProcess. Works well enough.
|
|
||||||
val intent =
|
|
||||||
Intent(applicationContext, MainActivity::class.java)
|
|
||||||
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
||||||
startActivity(intent)
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue