diff --git a/detekt-baseline.xml b/detekt-baseline.xml index a85a67ab..03b8db4c 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -59,7 +59,6 @@ NestedBlockDepth:DownloadFile.kt$DownloadFile.DownloadTask$override fun execute() NestedBlockDepth:DownloadHandler.kt$DownloadHandler$private fun downloadRecursively( fragment: Fragment, id: String, name: String?, isShare: Boolean, isDirectory: Boolean, save: Boolean, append: Boolean, autoPlay: Boolean, shuffle: Boolean, background: Boolean, playNext: Boolean, unpin: Boolean, isArtist: Boolean ) NestedBlockDepth:MediaPlayerService.kt$MediaPlayerService$private fun setupOnSongCompletedHandler() - ReturnCount:CommunicationErrorHandler.kt$CommunicationErrorHandler.Companion$fun getErrorMessage(error: Throwable, context: Context): String ReturnCount:ServerRowAdapter.kt$ServerRowAdapter$ private fun popupMenuItemClick(menuItem: MenuItem, position: Int): Boolean ReturnCount:TrackCollectionFragment.kt$TrackCollectionFragment$override fun onContextItemSelected(menuItem: MenuItem): Boolean TooGenericExceptionCaught:DownloadFile.kt$DownloadFile$e: Exception @@ -74,7 +73,6 @@ TooManyFunctions:MediaPlayerService.kt$MediaPlayerService : Service TooManyFunctions:RESTMusicService.kt$RESTMusicService : MusicService TooManyFunctions:TrackCollectionFragment.kt$TrackCollectionFragment : Fragment - UtilityClassWithPublicConstructor:CommunicationErrorHandler.kt$CommunicationErrorHandler UtilityClassWithPublicConstructor:FragmentTitle.kt$FragmentTitle diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java b/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java index 3bd56c61..73fb2ff2 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/util/BackgroundTask.java @@ -20,7 +20,7 @@ package org.moire.ultrasonic.util; import android.app.Activity; import android.os.Handler; -import org.moire.ultrasonic.service.CommunicationErrorHandler; +import org.moire.ultrasonic.service.CommunicationErrorUtil; /** * @author Sindre Mehus @@ -54,12 +54,12 @@ public abstract class BackgroundTask implements ProgressListener protected void error(Throwable error) { - CommunicationErrorHandler.Companion.handleError(error, activity); + CommunicationErrorUtil.Companion.handleError(error, activity); } protected String getErrorMessage(Throwable error) { - return CommunicationErrorHandler.Companion.getErrorMessage(error, activity); + return CommunicationErrorUtil.Companion.getErrorMessage(error, activity); } @Override diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt index 693923ea..c1e5cc40 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/GenericListModel.kt @@ -18,7 +18,7 @@ import org.koin.core.component.inject import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ServerSetting import org.moire.ultrasonic.domain.MusicFolder -import org.moire.ultrasonic.service.CommunicationErrorHandler +import org.moire.ultrasonic.service.CommunicationErrorUtil import org.moire.ultrasonic.service.MusicService import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.util.Settings @@ -94,7 +94,7 @@ open class GenericListModel(application: Application) : private fun handleException(exception: Exception, context: Context) { Handler(Looper.getMainLooper()).post { - CommunicationErrorHandler.handleError(exception, context) + CommunicationErrorUtil.handleError(exception, context) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt index 14cbbdf5..7c879a6b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/PlayerFragment.kt @@ -51,6 +51,8 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit import kotlin.math.abs import kotlin.math.max +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.koin.android.ext.android.inject import org.koin.core.component.KoinComponent @@ -65,6 +67,7 @@ import org.moire.ultrasonic.domain.RepeatMode import org.moire.ultrasonic.featureflags.Feature import org.moire.ultrasonic.featureflags.FeatureStorage import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle +import org.moire.ultrasonic.service.CommunicationErrorUtil import org.moire.ultrasonic.service.DownloadFile import org.moire.ultrasonic.service.LocalMediaPlayer import org.moire.ultrasonic.service.MediaPlayerController @@ -76,19 +79,18 @@ import org.moire.ultrasonic.subsonic.ShareHandler import org.moire.ultrasonic.util.CancellationToken import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Settings -import org.moire.ultrasonic.util.SilentBackgroundTask import org.moire.ultrasonic.util.Util import org.moire.ultrasonic.view.AutoRepeatButton import org.moire.ultrasonic.view.SongListAdapter import org.moire.ultrasonic.view.VisualizerView import timber.log.Timber +import java.util.concurrent.CancellationException /** * Contains the Music Player screen of Ultrasonic with playback controls and the playlist * * TODO: This class was more or less straight converted from Java legacy code. * There are many places where further cleanup would be nice. - * The usage of threads and SilentBackgroundTask can be replaced with Coroutines. */ @Suppress("LargeClass", "TooManyFunctions", "MagicNumber") class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinComponent { @@ -112,8 +114,9 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon private lateinit var executorService: ScheduledExecutorService private var currentPlaying: DownloadFile? = null private var currentSong: MusicDirectory.Entry? = null - private var onProgressChangedTask: SilentBackgroundTask? = null + private var onProgressChangedTask: Job? = null private var rxBusSubscription: Disposable? = null + private val scope: CoroutineScope = viewLifecycleOwner.lifecycleScope // Views and UI Elements private lateinit var visualizerViewLayout: LinearLayout @@ -233,17 +236,11 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon previousButton.setOnClickListener { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - mediaPlayerController.previous() - return null - } - - override fun done(result: Void?) { - onCurrentChanged() - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.previous() + onCurrentChanged() + onSliderProgressChanged() + } } previousButton.setOnRepeatListener { @@ -253,19 +250,11 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon nextButton.setOnClickListener { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Boolean { - mediaPlayerController.next() - return true - } - - override fun done(result: Boolean?) { - if (result == true) { - onCurrentChanged() - onSliderProgressChanged() - } - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.next() + onCurrentChanged() + onSliderProgressChanged() + } } nextButton.setOnRepeatListener { @@ -273,44 +262,26 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon changeProgress(incrementTime) } pauseButton.setOnClickListener { - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - mediaPlayerController.pause() - return null - } - - override fun done(result: Void?) { - onCurrentChanged() - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.pause() + onCurrentChanged() + onSliderProgressChanged() + } } stopButton.setOnClickListener { - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - mediaPlayerController.reset() - return null - } - - override fun done(result: Void?) { - onCurrentChanged() - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.reset() + onCurrentChanged() + onSliderProgressChanged() + } } startButton.setOnClickListener { networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - start() - return null - } - - override fun done(result: Void?) { - onCurrentChanged() - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + start() + onCurrentChanged() + onSliderProgressChanged() + } } shuffleButton.setOnClickListener { mediaPlayerController.shuffle() @@ -338,16 +309,10 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon progressBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onStopTrackingTouch(seekBar: SeekBar) { - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - mediaPlayerController.seekTo(progressBar.progress) - return null - } - - override fun done(result: Void?) { - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.seekTo(progressBar.progress) + onSliderProgressChanged() + } } override fun onStartTrackingTouch(seekBar: SeekBar) {} @@ -356,17 +321,11 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon playlistView.setOnItemClickListener { _, _, position, _ -> networkAndStorageChecker.warnIfNetworkOrStorageUnavailable() - object : SilentBackgroundTask(activity) { - override fun doInBackground(): Void? { - mediaPlayerController.play(position) - return null - } - - override fun done(result: Void?) { - onCurrentChanged() - onSliderProgressChanged() - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + mediaPlayerController.play(position) + onCurrentChanged() + onSliderProgressChanged() + } } registerForContextMenu(playlistView) @@ -429,7 +388,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon } // Query the Jukebox state off-thread - viewLifecycleOwner.lifecycleScope.launch { + scope.launch(CommunicationErrorUtil.handler(context)) { try { jukeboxAvailable = mediaPlayerController.isJukeboxAvailable } catch (all: Exception) { @@ -819,33 +778,28 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon private fun savePlaylistInBackground(playlistName: String) { Util.toast(context, resources.getString(R.string.download_playlist_saving, playlistName)) mediaPlayerController.suggestedPlaylistName = playlistName - object : SilentBackgroundTask(activity) { - @Throws(Throwable::class) - override fun doInBackground(): Void? { - val entries: MutableList = LinkedList() - for (downloadFile in mediaPlayerController.playList) { - entries.add(downloadFile.song) - } - val musicService = getMusicService() - musicService.createPlaylist(null, playlistName, entries) - return null - } - override fun done(result: Void?) { + scope.launch { + val entries: MutableList = LinkedList() + for (downloadFile in mediaPlayerController.playList) { + entries.add(downloadFile.song) + } + val musicService = getMusicService() + musicService.createPlaylist(null, playlistName, entries) + }.invokeOnCompletion { + if (it == null || it is CancellationException) { Util.toast(context, R.string.download_playlist_done) - } - - override fun error(error: Throwable) { - Timber.e(error, "Exception has occurred in savePlaylistInBackground") + } else { + Timber.e(it, "Exception has occurred in savePlaylistInBackground") val msg = String.format( Locale.ROOT, "%s %s", resources.getString(R.string.download_playlist_error), - getErrorMessage(error) + CommunicationErrorUtil.getErrorMessage(it, context) ) Util.toast(context, msg) } - }.execute() + } } private fun toggleFullScreenAlbumArt() { @@ -979,116 +933,96 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon if (onProgressChangedTask != null) { return } - onProgressChangedTask = object : SilentBackgroundTask(activity) { - var isJukeboxEnabled = false - var millisPlayed = 0 - var duration: Int? = null - var playerState: PlayerState? = null - override fun doInBackground(): Void? { - isJukeboxEnabled = mediaPlayerController.isJukeboxEnabled - millisPlayed = max(0, mediaPlayerController.playerPosition) - duration = mediaPlayerController.playerDuration - playerState = mediaPlayerController.playerState - return null + + onProgressChangedTask = scope.launch(CommunicationErrorUtil.handler(context)) { + + val isJukeboxEnabled: Boolean = mediaPlayerController.isJukeboxEnabled + val millisPlayed = max(0, mediaPlayerController.playerPosition) + val duration = mediaPlayerController.playerDuration + val playerState = mediaPlayerController.playerState + + if (cancellationToken.isCancellationRequested) return@launch + if (currentPlaying != null) { + positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true) + durationTextView.text = Util.formatTotalDuration(duration.toLong(), true) + progressBar.max = + if (duration == 0) 100 else duration // Work-around for apparent bug. + progressBar.progress = millisPlayed + progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled + } else { + positionTextView.setText(R.string.util_zero_time) + durationTextView.setText(R.string.util_no_time) + progressBar.progress = 0 + progressBar.max = 0 + progressBar.isEnabled = false } - @Suppress("LongMethod") - override fun done(result: Void?) { - if (cancellationToken.isCancellationRequested) return - if (currentPlaying != null) { - val millisTotal = if (duration == null) 0 else duration!! - positionTextView.text = Util.formatTotalDuration(millisPlayed.toLong(), true) - durationTextView.text = Util.formatTotalDuration(millisTotal.toLong(), true) - progressBar.max = - if (millisTotal == 0) 100 else millisTotal // Work-around for apparent bug. - progressBar.progress = millisPlayed - progressBar.isEnabled = currentPlaying!!.isWorkDone || isJukeboxEnabled - } else { - positionTextView.setText(R.string.util_zero_time) - durationTextView.setText(R.string.util_no_time) - progressBar.progress = 0 - progressBar.max = 0 - progressBar.isEnabled = false - } - - when (playerState) { - PlayerState.DOWNLOADING -> { - val progress = - if (currentPlaying != null) currentPlaying!!.progress.value!! else 0 - val downloadStatus = resources.getString( - R.string.download_playerstate_downloading, - Util.formatPercentage(progress) - ) - setTitle(this@PlayerFragment, downloadStatus) - } - PlayerState.PREPARING -> setTitle( - this@PlayerFragment, - R.string.download_playerstate_buffering + when (playerState) { + PlayerState.DOWNLOADING -> { + val progress = + if (currentPlaying != null) currentPlaying!!.progress.value!! else 0 + val downloadStatus = resources.getString( + R.string.download_playerstate_downloading, + Util.formatPercentage(progress) ) - PlayerState.STARTED -> { - if (mediaPlayerController.isShufflePlayEnabled) { - setTitle( - this@PlayerFragment, - R.string.download_playerstate_playing_shuffle - ) - } else { - setTitle(this@PlayerFragment, R.string.common_appname) - } - } - PlayerState.IDLE, - PlayerState.PREPARED, - PlayerState.STOPPED, - PlayerState.PAUSED, - PlayerState.COMPLETED -> { - } - else -> setTitle(this@PlayerFragment, R.string.common_appname) + setTitle(this@PlayerFragment, downloadStatus) } - - when (playerState) { - PlayerState.STARTED -> { - pauseButton.isVisible = true - stopButton.isVisible = false - startButton.isVisible = false - } - PlayerState.DOWNLOADING, PlayerState.PREPARING -> { - pauseButton.isVisible = false - stopButton.isVisible = true - startButton.isVisible = false - } - else -> { - pauseButton.isVisible = false - stopButton.isVisible = false - startButton.isVisible = true + PlayerState.PREPARING -> setTitle( + this@PlayerFragment, + R.string.download_playerstate_buffering + ) + PlayerState.STARTED -> { + if (mediaPlayerController.isShufflePlayEnabled) { + setTitle( + this@PlayerFragment, + R.string.download_playerstate_playing_shuffle + ) + } else { + setTitle(this@PlayerFragment, R.string.common_appname) } } - - // TODO: It would be a lot nicer if MediaPlayerController would send an event - // when this is necessary instead of updating every time - displaySongRating() - onProgressChangedTask = null + PlayerState.IDLE, + PlayerState.PREPARED, + PlayerState.STOPPED, + PlayerState.PAUSED, + PlayerState.COMPLETED -> { + } + else -> setTitle(this@PlayerFragment, R.string.common_appname) } + + when (playerState) { + PlayerState.STARTED -> { + pauseButton.isVisible = true + stopButton.isVisible = false + startButton.isVisible = false + } + PlayerState.DOWNLOADING, PlayerState.PREPARING -> { + pauseButton.isVisible = false + stopButton.isVisible = true + startButton.isVisible = false + } + else -> { + pauseButton.isVisible = false + stopButton.isVisible = false + startButton.isVisible = true + } + } + + // TODO: It would be a lot nicer if MediaPlayerController would send an event + // when this is necessary instead of updating every time + displaySongRating() + onProgressChangedTask = null } - onProgressChangedTask!!.execute() } private fun changeProgress(ms: Int) { - object : SilentBackgroundTask(activity) { - var msPlayed = 0 - var duration: Int? = null - var seekTo = 0 - override fun doInBackground(): Void? { - msPlayed = max(0, mediaPlayerController.playerPosition) - duration = mediaPlayerController.playerDuration - val msTotal = duration!! - seekTo = (msPlayed + ms).coerceAtMost(msTotal) - mediaPlayerController.seekTo(seekTo) - return null - } - - override fun done(result: Void?) { - progressBar.progress = seekTo - } - }.execute() + scope.launch(CommunicationErrorUtil.handler(context)) { + val msPlayed: Int = max(0, mediaPlayerController.playerPosition) + val duration = mediaPlayerController.playerDuration + val seekTo = (msPlayed + ms).coerceAtMost(duration) + mediaPlayerController.seekTo(seekTo) + progressBar.progress = seekTo + } } override fun onDown(me: MotionEvent): Boolean { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt index 9d785a30..e8f6d534 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -38,7 +38,7 @@ import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.domain.MusicDirectory import org.moire.ultrasonic.fragment.FragmentTitle.Companion.getTitle import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle -import org.moire.ultrasonic.service.CommunicationErrorHandler +import org.moire.ultrasonic.service.CommunicationErrorUtil import org.moire.ultrasonic.service.MediaPlayerController import org.moire.ultrasonic.subsonic.DownloadHandler import org.moire.ultrasonic.subsonic.ImageLoaderProvider @@ -211,7 +211,7 @@ class TrackCollectionFragment : Fragment() { val handler = CoroutineExceptionHandler { _, exception -> Handler(Looper.getMainLooper()).post { - CommunicationErrorHandler.handleError(exception, context) + CommunicationErrorUtil.handleError(exception, context) } refreshAlbumListView!!.isRefreshing = false } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorUtil.kt similarity index 80% rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorUtil.kt index bfda4aab..de3f9c84 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/CommunicationErrorUtil.kt @@ -20,12 +20,16 @@ package org.moire.ultrasonic.service import android.app.AlertDialog import android.content.Context +import android.os.Handler +import android.os.Looper import com.fasterxml.jackson.core.JsonParseException import java.io.FileNotFoundException import java.io.IOException import java.security.cert.CertPathValidatorException import java.security.cert.CertificateException import javax.net.ssl.SSLException +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineExceptionHandler import org.moire.ultrasonic.R import org.moire.ultrasonic.api.subsonic.ApiNotSupportedException import org.moire.ultrasonic.api.subsonic.SubsonicRESTException @@ -37,8 +41,19 @@ import timber.log.Timber * Contains helper functions to handle the exceptions * thrown during the communication with a Subsonic server */ -class CommunicationErrorHandler { +@Suppress("ReturnCount", "UtilityClassWithPublicConstructor") +class CommunicationErrorUtil { companion object { + fun handler(context: Context?, handler: ((CoroutineContext, Throwable) -> Unit)? = null): + CoroutineExceptionHandler { + return CoroutineExceptionHandler { coroutineContext, exception -> + Handler(Looper.getMainLooper()).post { + handleError(exception, context) + handler?.invoke(coroutineContext, exception) + } + } + } + fun handleError(error: Throwable?, context: Context?) { Timber.w(error) @@ -53,7 +68,8 @@ class CommunicationErrorHandler { .create().show() } - fun getErrorMessage(error: Throwable, context: Context): String { + fun getErrorMessage(error: Throwable, context: Context?): String { + if (context == null) return "Couldn't get Error message, Context is null" if (error is IOException && !Util.isNetworkConnected()) { return context.resources.getString(R.string.background_task_no_network) } else if (error is FileNotFoundException) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt deleted file mode 100644 index 3639aa2c..00000000 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/SilentBackgroundTask.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SilentBackgroundTask.kt - * Copyright (C) 2009-2021 Ultrasonic developers - * - * Distributed under terms of the GNU GPLv3 license. - */ - -package org.moire.ultrasonic.util - -import android.app.Activity - -/** - * @author Sindre Mehus - */ -abstract class SilentBackgroundTask(activity: Activity?) : BackgroundTask(activity) { - override fun execute() { - val thread: Thread = object : Thread() { - override fun run() { - try { - val result = doInBackground() - handler.post { done(result) } - } catch (all: Throwable) { - handler.post { error(all) } - } - } - } - thread.start() - } - - override fun updateProgress(messageId: Int) {} - override fun updateProgress(message: String) {} -}