Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ buildscript {
}

plugins {
id 'com.android.application' version '8.10.1' apply false
id 'com.android.library' version '8.10.1' apply false
id 'com.android.application' version '8.11.0' apply false
id 'com.android.library' version '8.11.0' apply false
id 'org.jetbrains.kotlin.android' version "$kotlin_version" apply false
id 'org.jetbrains.kotlin.plugin.compose' version "$kotlin_version" apply false
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-all.zip
24 changes: 12 additions & 12 deletions projectBlueWater/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
id "de.mannodermaus.android-junit5" version "1.9.3.0"
id 'org.jetbrains.kotlin.plugin.serialization' version '2.1.0'
id "de.mannodermaus.android-junit5" version "1.13.0.0"
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.0'
}

apply plugin: 'com.android.application'
Expand Down Expand Up @@ -167,16 +167,16 @@ def getGeneratedVersionCode() {
}

dependencies {
def compose_version = '1.8.2'
def media3_version = '1.6.1'
def lifecycle_version = '2.9.0'
def junit5_version = '5.12.2'
def compose_version = '1.8.3'
def media3_version = '1.7.1'
def lifecycle_version = '2.9.1'
def junit5_version = '5.13.2'

coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'

implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.annotation:annotation:1.9.1'
implementation 'androidx.work:work-runtime:2.10.1'
implementation 'androidx.work:work-runtime:2.10.2'
implementation 'androidx.media:media:1.7.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
Expand All @@ -194,11 +194,11 @@ dependencies {
implementation 'com.namehillsoftware:handoff:0.30.1'
implementation 'io.reactivex.rxjava3:rxjava:3.1.10'
implementation 'com.namehillsoftware:lazy-j:0.11.0'
implementation 'org.jsoup:jsoup:1.20.1'
implementation 'org.jsoup:jsoup:1.21.1'
implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation "androidx.media3:media3-datasource-okhttp:$media3_version"
implementation 'com.squareup.okio:okio:3.11.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.14'
implementation 'com.squareup.okio:okio:3.13.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.16'
implementation 'com.namehillsoftware:querydroid:0.6.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
Expand All @@ -214,11 +214,11 @@ dependencies {
testImplementation 'commons-codec:commons-codec:1.18.0'
testImplementation "org.junit.jupiter:junit-jupiter-api:$junit5_version"
testImplementation 'org.assertj:assertj-core:3.27.3'
testImplementation 'org.robolectric:robolectric:4.14.1'
testImplementation 'org.robolectric:robolectric:4.15.1'
testImplementation 'androidx.test.ext:junit-ktx:1.2.1'
testImplementation 'androidx.test:core:1.6.1'
testImplementation 'androidx.test:runner:1.6.2'
testImplementation 'io.mockk:mockk:1.14.2'
testImplementation 'io.mockk:mockk:1.14.4'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2'
testImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
testRuntimeOnly "org.junit.platform:junit-platform-launcher"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.lasthopesoftware.bluewater.client.playback.nowplaying.view.viewmodels
import com.lasthopesoftware.bluewater.client.playback.service.ControlPlaybackService
import com.lasthopesoftware.bluewater.client.stored.library.items.AccessStoredItems
import com.lasthopesoftware.bluewater.client.stored.sync.ScheduleSyncs
import com.lasthopesoftware.bluewater.exceptions.AnnounceExceptions
import com.lasthopesoftware.bluewater.settings.repository.access.HoldApplicationSettings
import com.lasthopesoftware.bluewater.shared.images.ProvideDefaultImage
import com.lasthopesoftware.bluewater.shared.messages.application.RegisterForApplicationMessages
Expand Down Expand Up @@ -59,5 +60,6 @@ interface ApplicationDependencies {
val audioCacheStreamSupplier: DiskFileCacheStreamSupplier
val okHttpClients: OkHttpFactory
val libraryNameLookup: LibraryNameLookup
val exceptionAnnouncer: AnnounceExceptions
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.lasthopesoftware.bluewater

import android.annotation.SuppressLint
import android.content.Context
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
Expand Down Expand Up @@ -45,6 +46,7 @@ import com.lasthopesoftware.bluewater.client.playback.nowplaying.view.viewmodels
import com.lasthopesoftware.bluewater.client.playback.service.PlaybackServiceController
import com.lasthopesoftware.bluewater.client.stored.library.items.StoredItemAccess
import com.lasthopesoftware.bluewater.client.stored.sync.SyncSchedulerInitializer
import com.lasthopesoftware.bluewater.exceptions.UnexpectedExceptionToaster
import com.lasthopesoftware.bluewater.settings.repository.access.ApplicationSettingsRepository
import com.lasthopesoftware.bluewater.settings.repository.access.CachingApplicationSettingsRepository
import com.lasthopesoftware.bluewater.shared.cls
Expand All @@ -65,6 +67,7 @@ object ApplicationDependenciesContainer {

private val sync = Any()

@SuppressLint("StaticFieldLeak")
@Volatile
@OptIn(UnstableApi::class)
private var attachedDependencies: AttachedDependencies? = null
Expand Down Expand Up @@ -167,6 +170,8 @@ object ApplicationDependenciesContainer {

override val libraryNameLookup by lazy { LibraryNameLookup(librarySettingsProvider) }

override val exceptionAnnouncer by lazy { UnexpectedExceptionToaster(context) }

override val storedItemAccess by lazy { StoredItemAccess(context) }

override val defaultImageProvider by lazy { DefaultImageProvider(context) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import ch.qos.logback.core.rolling.RollingFileAppender
import ch.qos.logback.core.rolling.TimeBasedRollingPolicy
import ch.qos.logback.core.util.StatusPrinter
import com.lasthopesoftware.bluewater.ApplicationDependenciesContainer.applicationDependencies
import com.lasthopesoftware.bluewater.exceptions.UncaughtExceptionHandlerLogger
import com.lasthopesoftware.bluewater.settings.ApplicationSettingsUpdated
import com.lasthopesoftware.bluewater.settings.repository.ApplicationSettings
import com.lasthopesoftware.bluewater.shared.exceptions.UncaughtExceptionHandlerLogger
import com.lasthopesoftware.bluewater.shared.lazyLogger
import com.lasthopesoftware.bluewater.shared.messages.registerReceiver
import com.lasthopesoftware.compilation.DebugFlag
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package com.lasthopesoftware.bluewater.shared.android.services
package com.lasthopesoftware.bluewater.android.services

class BindingUnexpectedlyDiedException(clazz: Class<*>) : Exception("Binding for ${clazz.canonicalName} unexpectedly died.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.lasthopesoftware.bluewater.android.services

interface ControlService {
fun stop()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.lasthopesoftware.bluewater.shared.android.services
package com.lasthopesoftware.bluewater.android.services

import android.app.Service
import android.os.Binder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.lasthopesoftware.bluewater.shared.android.services
package com.lasthopesoftware.bluewater.android.services

class InvalidBindingException(clazz: Class<*>)
: Exception("Binding could not cast to ${clazz.canonicalName}.")
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.lasthopesoftware.bluewater.shared.android.services
package com.lasthopesoftware.bluewater.android.services

import android.app.Service
import android.content.ComponentName
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.lasthopesoftware.bluewater.shared.android.services
package com.lasthopesoftware.bluewater.android.services

class UnexpectedNullBindingException(clazz: Class<*>)
: Exception("Unexpected null binding for ${clazz.canonicalName}.")
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
Expand Down Expand Up @@ -67,10 +66,9 @@ import com.lasthopesoftware.bluewater.client.connection.session.initialization.D
import com.lasthopesoftware.bluewater.client.playback.nowplaying.view.NowPlayingView
import com.lasthopesoftware.bluewater.client.settings.LibrarySettingsView
import com.lasthopesoftware.bluewater.client.settings.PermissionsDependencies
import com.lasthopesoftware.bluewater.exceptions.UncaughtExceptionHandlerLogger
import com.lasthopesoftware.bluewater.settings.ApplicationSettingsView
import com.lasthopesoftware.bluewater.settings.hidden.HiddenSettingsView
import com.lasthopesoftware.bluewater.shared.exceptions.UncaughtExceptionHandlerLogger
import com.lasthopesoftware.bluewater.shared.exceptions.UnexpectedExceptionToaster
import com.lasthopesoftware.bluewater.shared.observables.subscribeAsState
import com.lasthopesoftware.policies.ratelimiting.RateLimitingExecutionPolicy
import com.lasthopesoftware.promises.extensions.suspend
Expand Down Expand Up @@ -158,8 +156,7 @@ private fun BrowserLibraryDestination.Navigate(
libraryId = libraryId,
)

val context = LocalContext.current
LaunchedEffect(key1 = libraryId, key2 = context) {
LaunchedEffect(key1 = libraryId) {
try {
nowPlayingFilePropertiesViewModel.initializeViewModel(libraryId).suspend()
} catch (e: Throwable) {
Expand All @@ -169,7 +166,7 @@ private fun BrowserLibraryDestination.Navigate(
}

UncaughtExceptionHandlerLogger.uncaughtException(e) -> {
UnexpectedExceptionToaster.announce(context, e)
exceptionAnnouncer.announce(e)
}
}
}
Expand Down Expand Up @@ -235,8 +232,7 @@ fun LibraryDestination.Navigate(
undoBackStack = undoBackStackBuilder,
)

val context = LocalContext.current
LaunchedEffect(key1 = libraryId, key2 = context) {
LaunchedEffect(key1 = libraryId) {
try {
if (connectionWatcherViewModel.watchLibraryConnection(libraryId).suspend()) {
Promise.whenAll(
Expand All @@ -253,7 +249,7 @@ fun LibraryDestination.Navigate(
}

UncaughtExceptionHandlerLogger.uncaughtException(e) -> {
UnexpectedExceptionToaster.announce(context, e)
exceptionAnnouncer.announce(e)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.support.v4.media.MediaBrowserCompat
import androidx.media.MediaBrowserServiceCompat
import com.lasthopesoftware.bluewater.ApplicationDependenciesContainer.applicationDependencies
import com.lasthopesoftware.bluewater.R
import com.lasthopesoftware.bluewater.android.services.promiseBoundService
import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile
import com.lasthopesoftware.bluewater.client.browsing.files.properties.LibraryFilePropertiesDependentsRegistry
import com.lasthopesoftware.bluewater.client.browsing.items.ItemId
Expand All @@ -13,7 +14,6 @@ import com.lasthopesoftware.bluewater.client.connection.libraries.LibraryConnect
import com.lasthopesoftware.bluewater.client.connection.libraries.RateLimitedFilePropertiesDependencies
import com.lasthopesoftware.bluewater.shared.MagicPropertyBuilder
import com.lasthopesoftware.bluewater.shared.android.MediaSession.MediaSessionService
import com.lasthopesoftware.bluewater.shared.android.services.promiseBoundService
import com.lasthopesoftware.bluewater.shared.cls
import com.lasthopesoftware.bluewater.shared.lazyLogger
import com.lasthopesoftware.policies.ratelimiting.RateLimitingExecutionPolicy
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.lasthopesoftware.bluewater.client.connection.libraries

import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.shared.exceptions.DataNotReturnedException
import com.lasthopesoftware.bluewater.exceptions.DataNotReturnedException

class UrlKeyNotReturnedException(libraryId: LibraryId, key: Any?) : DataNotReturnedException("A UrlKeyHolder could not be provided for $libraryId and $key.")
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import com.lasthopesoftware.bluewater.client.connection.url.UrlBuilder.addPath
import com.lasthopesoftware.bluewater.client.connection.url.UrlBuilder.withMcApi
import com.lasthopesoftware.bluewater.client.connection.url.UrlKeyHolder
import com.lasthopesoftware.bluewater.client.servers.version.SemanticVersion
import com.lasthopesoftware.bluewater.exceptions.HttpResponseException
import com.lasthopesoftware.bluewater.shared.StandardResponse.Companion.toStandardResponse
import com.lasthopesoftware.bluewater.shared.exceptions.HttpResponseException
import com.lasthopesoftware.bluewater.shared.lazyLogger
import com.lasthopesoftware.exceptions.isOkHttpCanceled
import com.lasthopesoftware.policies.retries.RetryOnRejectionLazyPromise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import com.lasthopesoftware.bluewater.client.connection.url.UrlBuilder.addPath
import com.lasthopesoftware.bluewater.client.connection.url.UrlBuilder.withSubsonicApi
import com.lasthopesoftware.bluewater.client.connection.url.UrlKeyHolder
import com.lasthopesoftware.bluewater.client.servers.version.SemanticVersion
import com.lasthopesoftware.bluewater.shared.exceptions.HttpResponseException
import com.lasthopesoftware.bluewater.exceptions.HttpResponseException
import com.lasthopesoftware.bluewater.shared.lazyLogger
import com.lasthopesoftware.exceptions.isOkHttpCanceled
import com.lasthopesoftware.policies.retries.RetryOnRejectionLazyPromise
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.lifecycle.LifecycleService
import com.lasthopesoftware.bluewater.R
import com.lasthopesoftware.bluewater.android.intents.getIntent
import com.lasthopesoftware.bluewater.android.intents.makePendingIntentImmutable
import com.lasthopesoftware.bluewater.android.services.GenericBinder
import com.lasthopesoftware.bluewater.android.services.promiseBoundService
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.connection.live.LiveServerConnection
import com.lasthopesoftware.bluewater.client.connection.session.ConnectionSessionManager
Expand All @@ -20,18 +22,16 @@ import com.lasthopesoftware.bluewater.shared.android.notifications.NoOpChannelAc
import com.lasthopesoftware.bluewater.shared.android.notifications.control.NotificationsController
import com.lasthopesoftware.bluewater.shared.android.notifications.notificationchannel.NotificationChannelActivator
import com.lasthopesoftware.bluewater.shared.android.notifications.notificationchannel.SharedChannelProperties
import com.lasthopesoftware.bluewater.shared.android.services.GenericBinder
import com.lasthopesoftware.bluewater.shared.android.services.promiseBoundService
import com.lasthopesoftware.bluewater.shared.cls
import com.lasthopesoftware.resources.closables.lazyScoped
import com.namehillsoftware.handoff.promises.Promise

class PollConnectionService : LifecycleService() {

companion object {
private val magicPropertyBuilder by lazy { MagicPropertyBuilder(cls<PollConnectionService>()) }
private val stopWaitingForConnectionAction by lazy {
MagicPropertyBuilder.buildMagicPropertyName<PollConnectionService>("stopWaitingForConnection")
}

private val stopWaitingForConnectionAction by lazy { magicPropertyBuilder.buildProperty("stopWaitingForConnection") }
fun pollSessionConnection(context: Context, libraryId: LibraryId): Promise<LiveServerConnection> = Promise.Proxy { cp ->
context.promiseBoundService<PollConnectionService>()
.also(cp::doCancel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ import java.util.concurrent.ConcurrentLinkedQueue
cacheWriter?.clear()
cacheWriter = CacheWriter(
cacheStreamSupplier.promiseCachedFileOutputStream(libraryId, key)
.then(forward()) {
logger.warn("There was an error opening the cache output stream for key $key", it)
.then(forward()) { e ->
logger.warn("There was an error opening the cache output stream for key $key", e)
null
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import androidx.media.AudioFocusRequestCompat
import androidx.media.AudioManagerCompat
import com.lasthopesoftware.bluewater.client.browsing.files.ServiceFile
import com.lasthopesoftware.bluewater.client.browsing.library.repository.LibraryId
import com.lasthopesoftware.bluewater.client.playback.errors.PlaybackResourceNotAvailableInTimeException
import com.lasthopesoftware.bluewater.client.playback.volume.IVolumeManagement
import com.lasthopesoftware.bluewater.shared.android.audiofocus.ControlAudioFocus
import com.lasthopesoftware.promises.PromiseDelay.Companion.delay
import com.lasthopesoftware.promises.extensions.unitResponse
import com.namehillsoftware.handoff.promises.Promise
import org.joda.time.Duration
import java.util.concurrent.TimeoutException

class AudioManagingPlaybackStateChanger(
private val innerPlaybackState: ChangePlaybackState,
Expand All @@ -24,6 +24,10 @@ class AudioManagingPlaybackStateChanger(
AutoCloseable,
AudioManager.OnAudioFocusChangeListener
{
companion object {
private val audioFocusTimeout by lazy { Duration.standardSeconds(10) }
}

private val lazyAudioRequest = lazy {
AudioFocusRequestCompat
.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)
Expand Down Expand Up @@ -106,9 +110,9 @@ class AudioManagingPlaybackStateChanger(
val promisedAudioFocus = audioFocus.promiseAudioFocus(lazyAudioRequest.value)
return Promise.whenAny(
promisedAudioFocus,
delay<Any?>(Duration.standardSeconds(10)).then { _ ->
delay<Any?>(audioFocusTimeout).then { _ ->
promisedAudioFocus.cancel()
throw TimeoutException("Unable to gain audio focus in 10s")
throw PlaybackResourceNotAvailableInTimeException("audio focus", audioFocusTimeout)
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ class PreparedPlaybackQueueResourceManagement(
private val playbackPreparerProvider: IPlayableFilePreparationSourceProvider,
private val preparedPlaybackQueueConfiguration: IPreparedPlaybackQueueConfiguration
) : ManagePlaybackQueues, ResettableCloseable {
@Volatile
private var preparedPlaybackQueue: PreparedPlayableFileQueue? = null

@Synchronized
override fun initializePreparedPlaybackQueue(positionedFileQueue: PositionedFileQueue): PreparedPlayableFileQueue {
reset()
return PreparedPlayableFileQueue(
Expand All @@ -18,9 +20,8 @@ class PreparedPlaybackQueueResourceManagement(
).also { preparedPlaybackQueue = it }
}

override fun tryUpdateQueue(positionedFileQueue: PositionedFileQueue): Boolean {
return preparedPlaybackQueue?.updateQueue(positionedFileQueue) != null
}
override fun tryUpdateQueue(positionedFileQueue: PositionedFileQueue): Boolean =
preparedPlaybackQueue?.updateQueue(positionedFileQueue) != null

override fun reset() {
preparedPlaybackQueue?.close()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.lasthopesoftware.bluewater.client.playback.errors

import org.joda.time.Duration
import java.util.concurrent.TimeoutException

class PlaybackResourceNotAvailableInTimeException(resource: String, waitingTime: Duration)
: TimeoutException("Resource \"$resource\" was not available in $waitingTime.")
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ class ExoPlayerPlaybackHandler(private val exoPlayer: PromisingExoPlayer) :
}
}
is ParserException -> {
if (cause.message?.startsWith("Searched too many bytes.") == true) {
if (cause.message.startsWith("Searched too many bytes.")) {
logger.warn("The stream was corrupted, completing playback", error)
resolve(this)
return
Expand Down
Loading