diff --git a/fastlane/metadata/android/en-US/changelogs/130.txt b/fastlane/metadata/android/en-US/changelogs/128.txt similarity index 100% rename from fastlane/metadata/android/en-US/changelogs/130.txt rename to fastlane/metadata/android/en-US/changelogs/128.txt diff --git a/fastlane/metadata/android/en-US/changelogs/129.txt b/fastlane/metadata/android/en-US/changelogs/129.txt new file mode 100644 index 00000000..99df21de --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/129.txt @@ -0,0 +1,12 @@ +### Fixes +- Fix a bug in 4.7.0 that repeat mode was activated by default. + +### Features +- Added custom buttons for shuffling the current queue and setting repeat mode (Android Auto) +- Properly handling nested directory structures (Android Auto) +- Add a toast when adding tracks to the playlist +- Allow pinning when offline + +### Dependencies +- Update koin +- Update media3 to v1.1.0 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6137c2c9..9b704ce0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -4,6 +4,7 @@ gradle = "8.1.1" navigation = "2.6.0" gradlePlugin = "8.1.0" +androidxcar = "1.2.0" androidxcore = "1.10.1" ktlint = "0.43.2" ktlintGradle = "11.5.0" @@ -49,6 +50,7 @@ kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin" ktlintGradle = { module = "org.jlleitschuh.gradle:ktlint-gradle", version.ref = "ktlintGradle" } detekt = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } +car = { module = "androidx.car.app:app", version.ref = "androidxcar" } core = { module = "androidx.core:core-ktx", version.ref = "androidxcore" } design = { module = "com.google.android.material:material", version.ref = "materialDesign" } annotations = { module = "androidx.annotation:annotation", version.ref = "androidSupport" } @@ -103,4 +105,4 @@ apacheCodecs = { module = "commons-codec:commons-codec", version.ref robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } [plugins] -ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } \ No newline at end of file +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index cea9d70f..5f9420ae 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -12,8 +12,8 @@ android { defaultConfig { applicationId "org.moire.ultrasonic" - versionCode 128 - versionName "4.7.0" + versionCode 129 + versionName "4.7.1" minSdkVersion versions.minSdk targetSdkVersion versions.targetSdk @@ -81,6 +81,7 @@ android { disable 'ObsoleteLintCustomCheck' // We manage dependencies on Gitlab with RenovateBot disable 'GradleDependency' + disable 'AndroidGradlePluginVersion' textReport true checkDependencies true } @@ -100,6 +101,7 @@ dependencies { exclude group: "com.android.support" } + implementation libs.car implementation libs.core implementation libs.design implementation libs.multidex diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml index 711f13e2..0d25405d 100644 --- a/ultrasonic/src/main/AndroidManifest.xml +++ b/ultrasonic/src/main/AndroidManifest.xml @@ -12,6 +12,8 @@ <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> + <uses-sdk tools:overrideLibrary="androidx.car.app" /> + <supports-screens android:anyDensity="true" android:largeScreens="true" diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt index 0154a05a..ee203b41 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/playback/AutoMediaBrowserCallback.kt @@ -7,7 +7,10 @@ package org.moire.ultrasonic.playback +import android.content.Context +import android.os.Build import android.os.Bundle +import androidx.car.app.connection.CarConnection import androidx.media3.common.HeartRating import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS @@ -97,6 +100,7 @@ const val PLAY_COMMAND = "play " @Suppress("TooManyFunctions", "LargeClass", "UnusedPrivateMember") class AutoMediaBrowserCallback : MediaLibraryService.MediaLibrarySession.Callback, KoinComponent { + private val applicationContext: Context by inject() private val activeServerProvider: ActiveServerProvider by inject() private val serviceJob = SupervisorJob() @@ -115,6 +119,7 @@ class AutoMediaBrowserCallback : MediaLibraryService.MediaLibrarySession.Callbac private val placeholderButton = getPlaceholderButton() private var heartIsCurrentlyOn = false + private var customRepeatModeSet = false // This button is used for an unstarred track, and its action will star the track private val heartButtonToggleOn = @@ -230,7 +235,7 @@ class AutoMediaBrowserCallback : MediaLibraryService.MediaLibrarySession.Callbac commandButton.sessionCommand?.let { availableSessionCommands.add(it) } } - session.player.repeatMode = Player.REPEAT_MODE_ALL + configureRepeatMode(session.player) return MediaSession.ConnectionResult.accept( availableSessionCommands.build(), @@ -238,6 +243,42 @@ class AutoMediaBrowserCallback : MediaLibraryService.MediaLibrarySession.Callbac ) } + private fun configureRepeatMode(player: Player) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Timber.d("Car app library available, observing CarConnection") + + val originalRepeatMode = player.repeatMode + + var lastCarConnectionType = -1 + + CarConnection(applicationContext).type.observeForever { + if (lastCarConnectionType == it) + return@observeForever + + lastCarConnectionType = it + + Timber.d("CarConnection type changed to %s", it) + + when (it) { + CarConnection.CONNECTION_TYPE_PROJECTION -> + if (!customRepeatModeSet) { + Timber.d("[CarConnection] Setting repeat mode to ALL") + player.repeatMode = Player.REPEAT_MODE_ALL + customRepeatModeSet = true + } + + CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> + if (customRepeatModeSet) { + Timber.d("[CarConnection] Resetting repeat mode") + player.repeatMode = originalRepeatMode + customRepeatModeSet = false + } + } + } + } else + Timber.d("Car app library not available") + } + override fun onPostConnect(session: MediaSession, controller: MediaSession.ControllerInfo) { if (controller.controllerVersion != 0) { // Let Media3 controller (for instance the MediaNotificationProvider) @@ -369,6 +410,7 @@ class AutoMediaBrowserCallback : MediaLibraryService.MediaLibrarySession.Callbac PlaybackService.CUSTOM_COMMAND_REPEAT_MODE -> { customCommandFuture = Futures.immediateFuture(SessionResult(RESULT_SUCCESS)) + customRepeatModeSet = true session.player.setNextRepeatMode() session.updateCustomCommands()