diff --git a/fastlane/metadata/android/en-US/changelogs/117.txt b/fastlane/metadata/android/en-US/changelogs/117.txt new file mode 100644 index 00000000..557e9b8b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/117.txt @@ -0,0 +1,8 @@ +Bug fixes +- Fix more exceptions + +Changes since 4.2.0 +- #827: Make app full compliant Android Auto to publish in Play Store. +- #878: "Play shuffled" option for playlists always begins with the first track. +- #891: Dump config to log file when logging is enabled. +- #854: Remove Videos menu option for servers which don't support it. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 716a1706..5661132f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ ktlint = "0.43.2" ktlintGradle = "11.3.1" detekt = "1.22.0" preferences = "1.2.0" -media3 = "1.0.0" +media3 = "1.0.1" androidSupport = "1.6.0" materialDesign = "1.8.0" @@ -23,7 +23,8 @@ viewModelKtx = "2.6.1" swipeRefresh = "1.1.0" retrofit = "2.9.0" -jackson = "2.14.2" +## KEEP ON 2.13 branch (https://github.com/FasterXML/jackson-databind/issues/3658#issuecomment-1312633064) for compatibility with API 24 +jackson = "2.13.5" okhttp = "4.10.0" koin = "3.3.2" picasso = "2.8" diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index d89368b3..e367497a 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -9,8 +9,8 @@ android { defaultConfig { applicationId "org.moire.ultrasonic" - versionCode 116 - versionName "4.3.3" + versionCode 117 + versionName "4.3.4" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java index 475bb602..eeb1c48b 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/view/GenreAdapter.java @@ -46,7 +46,7 @@ public class GenreAdapter extends ArrayAdapter implements SectionIndexer private final Object[] sections; private final Integer[] positions; - public GenreAdapter(Context context, List genres) + public GenreAdapter(@NonNull Context context, List genres) { super(context, R.layout.list_item_generic, genres); 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 b67ec05b..6664f17e 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/TrackCollectionFragment.kt @@ -401,6 +401,8 @@ open class TrackCollectionFragment( ) { // We are coming back from unknown context // and need to ensure Main Thread in order to manipulate the UI + // If view is null, our view was disposed in the meantime + if (view == null) return viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { val multipleSelection = viewAdapter.hasMultipleSelection() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/legacy/SelectGenreFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/legacy/SelectGenreFragment.kt index 31790ef3..0a876b45 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/legacy/SelectGenreFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/legacy/SelectGenreFragment.kt @@ -102,7 +102,9 @@ class SelectGenreFragment : Fragment() { override fun done(result: List) { emptyView!!.isVisible = result.isEmpty() - genreListView!!.adapter = GenreAdapter(context, result) + if (context != null) { + genreListView!!.adapter = GenreAdapter(context!!, result) + } } } task.execute() diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt index c814eeec..06bc30ff 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/ImageLoader.kt @@ -240,6 +240,8 @@ class ImageLoader( } finally { inputStream.safeClose() } + } catch (all: Exception) { + Timber.w(all) } finally { cacheInProgress.remove(file)?.countDown() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt index 286ce8f2..ce14c811 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/subsonic/DownloadHandler.kt @@ -7,11 +7,14 @@ package org.moire.ultrasonic.subsonic +import android.os.Handler +import android.os.Looper import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import java.util.Collections import java.util.LinkedList import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -28,6 +31,7 @@ import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator import org.moire.ultrasonic.util.InfoDialog import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Util +import timber.log.Timber /** * Retrieves a list of songs and adds them to the now playing list @@ -39,6 +43,16 @@ class DownloadHandler( ) : CoroutineScope by CoroutineScope(Dispatchers.IO) { private val maxSongs = 500 + /** + * Exception Handler for Coroutines + */ + val exceptionHandler = CoroutineExceptionHandler { _, exception -> + Handler(Looper.getMainLooper()).post { + Timber.w(exception) + } + } + + // TODO: Use coroutine here (with proper exception handler) fun download( fragment: Fragment, append: Boolean, @@ -210,7 +224,7 @@ class DownloadHandler( isArtist: Boolean ) { // Launch the Job - val job = launch { + val job = launch(exceptionHandler) { val songs: MutableList = getTracksFromServer(isArtist, id, isDirectory, name, isShare) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Dialogs.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Dialogs.kt index a33dae82..7988dc3c 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Dialogs.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Dialogs.kt @@ -10,7 +10,9 @@ package org.moire.ultrasonic.util import android.app.Activity import android.content.Context import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.lang.ref.WeakReference import org.moire.ultrasonic.R +import timber.log.Timber /* * InfoDialog can be used to show some information to the user. Typically it cannot be cancelled, @@ -19,24 +21,30 @@ import org.moire.ultrasonic.R open class InfoDialog( context: Context, message: CharSequence?, - private val activity: Activity? = null, + activity: Activity? = null, private val finishActivityOnClose: Boolean = false ) { - - open var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message) + private val activityRef: WeakReference = WeakReference(activity) + open var builder: MaterialAlertDialogBuilder = Builder(activityRef.get() ?: context, message) fun show() { builder.setOnCancelListener { if (finishActivityOnClose) { - activity!!.finish() + activityRef.get()?.finish() } } builder.setPositiveButton(R.string.common_ok) { _, _ -> if (finishActivityOnClose) { - activity!!.finish() + activityRef.get()?.finish() } } - builder.create().show() + + // If the app was put into the background in the meantime this would fail + try { + builder.create().show() + } catch (all: Exception) { + Timber.w(all, "Failed to create dialog") + } } class Builder(context: Context) : MaterialAlertDialogBuilder(context) { @@ -93,7 +101,6 @@ class ConfirmationDialog( activity: Activity? = null, finishActivityOnClose: Boolean = false ) : InfoDialog(context, message, activity, finishActivityOnClose) { - override var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message) class Builder(context: Context) : MaterialAlertDialogBuilder(context) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/StorageFile.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/StorageFile.kt index 09b12f3c..5d1eb983 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/StorageFile.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/StorageFile.kt @@ -273,7 +273,7 @@ class StorageFile( } private fun getStorageFileForParentDirectory(path: String): StorageFile? { - val parentPath = FileUtil.getParentPath(path)!! + val parentPath = FileUtil.getParentPath(path) ?: return null if (storageFilePathDictionary.containsKey(parentPath)) return storageFilePathDictionary[parentPath]!! if (notExistingPathDictionary.contains(parentPath)) return null diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index 6ba28594..80517991 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -133,19 +133,24 @@ object Util { @JvmStatic @SuppressLint("ShowToast") // Invalid warning fun toast(context: Context?, message: CharSequence?, shortDuration: Boolean) { - if (toast == null) { - toast = Toast.makeText( - context, - message, - if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG - ) - toast!!.setGravity(Gravity.CENTER, 0, 0) - } else { - toast!!.setText(message) - toast!!.duration = - if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG + // If called after doing some background processing, our context might have expired! + try { + if (toast == null) { + toast = Toast.makeText( + context, + message, + if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG + ) + toast!!.setGravity(Gravity.CENTER, 0, 0) + } else { + toast!!.setText(message) + toast!!.duration = + if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG + } + toast!!.show() + } catch (_: Exception) { + // Ignore } - toast!!.show() } /**