Skip to content

Commit

Permalink
android: Loader - load early (before service thread) both in activity…
Browse files Browse the repository at this point in the history
… and service.

Loader converted from java to kotlin.

Instead of loading libmpd when the service thread is started,
the service will not start the the thread if libmpd failed to load.

The loader is also accessed by the view data to let
the ui adjust if failed to load, by showing the failure reason
and disabling the Start MPD button.
  • Loading branch information
geneticdrift committed Feb 6, 2025
1 parent ae1c5e3 commit f1e43cb
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 60 deletions.
23 changes: 0 additions & 23 deletions android/app/src/main/java/org/musicpd/Loader.java

This file was deleted.

45 changes: 45 additions & 0 deletions android/app/src/main/java/org/musicpd/Loader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project
package org.musicpd

import android.content.Context
import android.os.Build
import android.util.Log

object Loader {
private const val TAG = "Loader"

private var loaded: Boolean = false
private var error: String? = null
private val failReason: String get() = error ?: ""

val isLoaded: Boolean get() = loaded

init {
load()
}

private fun load() {
if (loaded) return
loaded = try {
error = null
System.loadLibrary("mpd")
Log.i(TAG, "mpd lib loaded")
true
} catch (e: Throwable) {
error = e.message ?: e.javaClass.simpleName
Log.e(TAG, "failed to load mpd lib: $failReason")
false
}
}

fun loadFailureMessage(context: Context): String {
return context.getString(
R.string.mpd_load_failure_message,
Build.SUPPORTED_ABIS.joinToString(),
Build.PRODUCT,
Build.FINGERPRINT,
failReason
)
}
}
25 changes: 11 additions & 14 deletions android/app/src/main/java/org/musicpd/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class Main : Service(), Runnable {
}
}

private lateinit var mpdApp: MPDApplication
private lateinit var mpdLoader: Loader

private var mThread: Thread? = null
private var mStatus = MAIN_STATUS_STOPPED
private var mAbort = false
Expand Down Expand Up @@ -104,6 +107,11 @@ class Main : Service(), Runnable {
}
}

override fun onCreate() {
super.onCreate()
mpdLoader = Loader
}

@Synchronized
private fun sendMessage(
@Suppress("SameParameterValue") what: Int,
Expand Down Expand Up @@ -152,19 +160,6 @@ class Main : Service(), Runnable {
}

override fun run() {
if (!Loader.loaded) {
val error = """
Failed to load the native MPD library.
Report this problem to us, and include the following information:
SUPPORTED_ABIS=${java.lang.String.join(", ", *Build.SUPPORTED_ABIS)}
PRODUCT=${Build.PRODUCT}
FINGERPRINT=${Build.FINGERPRINT}
error=${Loader.error}
""".trimIndent()
setStatus(MAIN_STATUS_ERROR, error)
stopSelf()
return
}
synchronized(this) {
if (mAbort) return
setStatus(MAIN_STATUS_STARTED, null)
Expand Down Expand Up @@ -245,7 +240,9 @@ class Main : Service(), Runnable {
.setContentIntent(contentIntent)
.build()

mThread = Thread(this).apply { start() }
if (mpdLoader.isLoaded) {
mThread = Thread(this).apply { start() }
}

val player = MPDPlayer(Looper.getMainLooper())
mMediaSession = MediaSession.Builder(this, player).build()
Expand Down
2 changes: 2 additions & 0 deletions android/app/src/main/java/org/musicpd/ui/SettingsViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.musicpd.Loader
import org.musicpd.MainServiceClient
import org.musicpd.Preferences
import org.musicpd.data.LoggingRepository
Expand All @@ -17,6 +18,7 @@ class SettingsViewModel @Inject constructor(
private var loggingRepository: LoggingRepository
) : ViewModel() {
private var mClient: MainServiceClient? = null
val mpdLoader = Loader

data class StatusUiState(
val statusMessage: String = "",
Expand Down
70 changes: 58 additions & 12 deletions android/app/src/main/java/org/musicpd/ui/StatusScreen.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
package org.musicpd.ui

import android.Manifest
import android.content.Context
import android.os.Build
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Circle
import androidx.compose.material3.Button
Expand All @@ -20,6 +25,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
Expand Down Expand Up @@ -53,13 +59,24 @@ fun StatusScreen(settingsViewModel: SettingsViewModel) {
verticalArrangement = Arrangement.Center
) {
NetworkAddress()
ServerStatus(settingsViewModel)
ServerStatus(settingsViewModel, storagePermissionState)
AudioMediaPermission(storagePermissionState)
MPDLoaderStatus(settingsViewModel)
}
}

@ColorInt
fun getThemeColorAttribute(context: Context, @AttrRes attr: Int): Int {
val value = TypedValue()
if (context.theme.resolveAttribute(attr, value, true)) {
return value.data
}
return android.graphics.Color.BLACK
}

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun ServerStatus(settingsViewModel: SettingsViewModel) {
fun ServerStatus(settingsViewModel: SettingsViewModel, storagePermissionState: PermissionState) {
val context = LocalContext.current

val statusUiState by settingsViewModel.statusUIState.collectAsState()
Expand All @@ -72,21 +89,35 @@ fun ServerStatus(settingsViewModel: SettingsViewModel) {
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly
) {
Row {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Default.Circle,
contentDescription = "",
tint = if (statusUiState.running) Color(0xFFB8F397) else Color(0xFFFFDAD6)
tint = Color(
getThemeColorAttribute(
context,
if (statusUiState.running) R.attr.appColorPositive else R.attr.appColorNegative
)
),
modifier = Modifier
.padding(end = 8.dp)
.alpha(0.6f)
)
Text(text = if (statusUiState.running) "Running" else "Stopped")
Text(text = stringResource(id = if (statusUiState.running) R.string.running else R.string.stopped))
}
Button(onClick = {
if (statusUiState.running)
settingsViewModel.stopMPD()
else
settingsViewModel.startMPD(context)
}) {
Text(text = if (statusUiState.running) "Stop MPD" else "Start MPD")
Button(
onClick = {
if (statusUiState.running)
settingsViewModel.stopMPD()
else
settingsViewModel.startMPD(context)
},
enabled = settingsViewModel.mpdLoader.isLoaded
&& storagePermissionState.status.isGranted
) {
Text(
text = stringResource(id = if (statusUiState.running) R.string.stopMPD else R.string.startMPD)
)
}
}
Row(
Expand Down Expand Up @@ -139,4 +170,19 @@ fun AudioMediaPermission(storagePermissionState: PermissionState) {
}
}
}
}

@Composable
fun MPDLoaderStatus(settingsViewModel: SettingsViewModel) {
val loader = settingsViewModel.mpdLoader
if (!loader.isLoaded) {
val context = LocalContext.current
SelectionContainer {
Text(
loader.loadFailureMessage(context),
Modifier.padding(16.dp),
color = MaterialTheme.colorScheme.error
)
}
}
}
31 changes: 21 additions & 10 deletions android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>

<resources>
<string name="app_name">MPD</string>
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
<string name="notification_text_mpd_running">Touch for MPD options.</string>
<string name="toggle_button_run_on">MPD is running</string>
<string name="toggle_button_run_off">MPD is not running</string>
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
<string name="external_files_permission_request">MPD requires access to external files to play local music. Please grant the permission.</string>
<string name="title_open_app_info">Open app info</string>
<string name="app_name">MPD</string>
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
<string name="notification_text_mpd_running">Touch for MPD options.</string>
<string name="toggle_button_run_on">MPD is running</string>
<string name="toggle_button_run_off">MPD is not running</string>
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
<string name="external_files_permission_request">MPD requires access to external files to play local music. Please grant the permission.</string>
<string name="title_open_app_info">Open app info</string>
<string name="mpd_load_failure_message">"Failed to load the native MPD library.
Report this problem to us, and include the following information:
SUPPORTED_ABIS=%1$s
PRODUCT=%2$s
FINGERPRINT=%3$s
error=%4$s"
</string>
<string name="stopped">Stopped</string>
<string name="running">Running</string>
<string name="stopMPD">Stop MPD</string>
<string name="startMPD">Start MPD</string>
</resources>
19 changes: 18 additions & 1 deletion android/app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.MPD" parent="android:Theme.Material.Light.NoActionBar" />
<color name="red_500">#F44336</color>
<color name="red_900">#B71C1C</color>
<color name="green_300">#81C784</color>
<color name="green_700">#388E3C</color>

<color name="colorErrorOnLight">@color/red_900</color>
<color name="colorErrorOnDark">@color/red_500</color>

<color name="colorSuccessOnLight">@color/green_700</color>
<color name="colorSuccessOnDark">@color/green_300</color>

<attr name="appColorNegative" format="color|reference" />
<attr name="appColorPositive" format="color|reference" />

<style name="Theme.MPD" parent="android:Theme.Material.Light.NoActionBar">
<item name="appColorNegative">@color/colorErrorOnLight</item>
<item name="appColorPositive">@color/colorSuccessOnLight</item>
</style>
</resources>

0 comments on commit f1e43cb

Please sign in to comment.