Skip to content

Commit f1e43cb

Browse files
committed
android: Loader - load early (before service thread) both in activity 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.
1 parent ae1c5e3 commit f1e43cb

File tree

7 files changed

+155
-60
lines changed

7 files changed

+155
-60
lines changed

android/app/src/main/java/org/musicpd/Loader.java

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
// Copyright The Music Player Daemon Project
3+
package org.musicpd
4+
5+
import android.content.Context
6+
import android.os.Build
7+
import android.util.Log
8+
9+
object Loader {
10+
private const val TAG = "Loader"
11+
12+
private var loaded: Boolean = false
13+
private var error: String? = null
14+
private val failReason: String get() = error ?: ""
15+
16+
val isLoaded: Boolean get() = loaded
17+
18+
init {
19+
load()
20+
}
21+
22+
private fun load() {
23+
if (loaded) return
24+
loaded = try {
25+
error = null
26+
System.loadLibrary("mpd")
27+
Log.i(TAG, "mpd lib loaded")
28+
true
29+
} catch (e: Throwable) {
30+
error = e.message ?: e.javaClass.simpleName
31+
Log.e(TAG, "failed to load mpd lib: $failReason")
32+
false
33+
}
34+
}
35+
36+
fun loadFailureMessage(context: Context): String {
37+
return context.getString(
38+
R.string.mpd_load_failure_message,
39+
Build.SUPPORTED_ABIS.joinToString(),
40+
Build.PRODUCT,
41+
Build.FINGERPRINT,
42+
failReason
43+
)
44+
}
45+
}

android/app/src/main/java/org/musicpd/Main.kt

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ class Main : Service(), Runnable {
5959
}
6060
}
6161

62+
private lateinit var mpdApp: MPDApplication
63+
private lateinit var mpdLoader: Loader
64+
6265
private var mThread: Thread? = null
6366
private var mStatus = MAIN_STATUS_STOPPED
6467
private var mAbort = false
@@ -104,6 +107,11 @@ class Main : Service(), Runnable {
104107
}
105108
}
106109

110+
override fun onCreate() {
111+
super.onCreate()
112+
mpdLoader = Loader
113+
}
114+
107115
@Synchronized
108116
private fun sendMessage(
109117
@Suppress("SameParameterValue") what: Int,
@@ -152,19 +160,6 @@ class Main : Service(), Runnable {
152160
}
153161

154162
override fun run() {
155-
if (!Loader.loaded) {
156-
val error = """
157-
Failed to load the native MPD library.
158-
Report this problem to us, and include the following information:
159-
SUPPORTED_ABIS=${java.lang.String.join(", ", *Build.SUPPORTED_ABIS)}
160-
PRODUCT=${Build.PRODUCT}
161-
FINGERPRINT=${Build.FINGERPRINT}
162-
error=${Loader.error}
163-
""".trimIndent()
164-
setStatus(MAIN_STATUS_ERROR, error)
165-
stopSelf()
166-
return
167-
}
168163
synchronized(this) {
169164
if (mAbort) return
170165
setStatus(MAIN_STATUS_STARTED, null)
@@ -245,7 +240,9 @@ class Main : Service(), Runnable {
245240
.setContentIntent(contentIntent)
246241
.build()
247242

248-
mThread = Thread(this).apply { start() }
243+
if (mpdLoader.isLoaded) {
244+
mThread = Thread(this).apply { start() }
245+
}
249246

250247
val player = MPDPlayer(Looper.getMainLooper())
251248
mMediaSession = MediaSession.Builder(this, player).build()

android/app/src/main/java/org/musicpd/ui/SettingsViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
66
import kotlinx.coroutines.flow.MutableStateFlow
77
import kotlinx.coroutines.flow.StateFlow
88
import kotlinx.coroutines.flow.asStateFlow
9+
import org.musicpd.Loader
910
import org.musicpd.MainServiceClient
1011
import org.musicpd.Preferences
1112
import org.musicpd.data.LoggingRepository
@@ -17,6 +18,7 @@ class SettingsViewModel @Inject constructor(
1718
private var loggingRepository: LoggingRepository
1819
) : ViewModel() {
1920
private var mClient: MainServiceClient? = null
21+
val mpdLoader = Loader
2022

2123
data class StatusUiState(
2224
val statusMessage: String = "",

android/app/src/main/java/org/musicpd/ui/StatusScreen.kt

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
package org.musicpd.ui
22

33
import android.Manifest
4+
import android.content.Context
45
import android.os.Build
6+
import android.util.TypedValue
7+
import androidx.annotation.AttrRes
8+
import androidx.annotation.ColorInt
59
import androidx.compose.foundation.layout.Arrangement
610
import androidx.compose.foundation.layout.Column
711
import androidx.compose.foundation.layout.Row
812
import androidx.compose.foundation.layout.fillMaxSize
913
import androidx.compose.foundation.layout.fillMaxWidth
1014
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.text.selection.SelectionContainer
1116
import androidx.compose.material.icons.Icons
1217
import androidx.compose.material.icons.filled.Circle
1318
import androidx.compose.material3.Button
@@ -20,6 +25,7 @@ import androidx.compose.runtime.collectAsState
2025
import androidx.compose.runtime.getValue
2126
import androidx.compose.ui.Alignment
2227
import androidx.compose.ui.Modifier
28+
import androidx.compose.ui.draw.alpha
2329
import androidx.compose.ui.graphics.Color
2430
import androidx.compose.ui.platform.LocalContext
2531
import androidx.compose.ui.res.stringResource
@@ -53,13 +59,24 @@ fun StatusScreen(settingsViewModel: SettingsViewModel) {
5359
verticalArrangement = Arrangement.Center
5460
) {
5561
NetworkAddress()
56-
ServerStatus(settingsViewModel)
62+
ServerStatus(settingsViewModel, storagePermissionState)
5763
AudioMediaPermission(storagePermissionState)
64+
MPDLoaderStatus(settingsViewModel)
5865
}
5966
}
6067

68+
@ColorInt
69+
fun getThemeColorAttribute(context: Context, @AttrRes attr: Int): Int {
70+
val value = TypedValue()
71+
if (context.theme.resolveAttribute(attr, value, true)) {
72+
return value.data
73+
}
74+
return android.graphics.Color.BLACK
75+
}
76+
77+
@OptIn(ExperimentalPermissionsApi::class)
6178
@Composable
62-
fun ServerStatus(settingsViewModel: SettingsViewModel) {
79+
fun ServerStatus(settingsViewModel: SettingsViewModel, storagePermissionState: PermissionState) {
6380
val context = LocalContext.current
6481

6582
val statusUiState by settingsViewModel.statusUIState.collectAsState()
@@ -72,21 +89,35 @@ fun ServerStatus(settingsViewModel: SettingsViewModel) {
7289
verticalAlignment = Alignment.CenterVertically,
7390
horizontalArrangement = Arrangement.SpaceEvenly
7491
) {
75-
Row {
92+
Row(verticalAlignment = Alignment.CenterVertically) {
7693
Icon(
7794
imageVector = Icons.Default.Circle,
7895
contentDescription = "",
79-
tint = if (statusUiState.running) Color(0xFFB8F397) else Color(0xFFFFDAD6)
96+
tint = Color(
97+
getThemeColorAttribute(
98+
context,
99+
if (statusUiState.running) R.attr.appColorPositive else R.attr.appColorNegative
100+
)
101+
),
102+
modifier = Modifier
103+
.padding(end = 8.dp)
104+
.alpha(0.6f)
80105
)
81-
Text(text = if (statusUiState.running) "Running" else "Stopped")
106+
Text(text = stringResource(id = if (statusUiState.running) R.string.running else R.string.stopped))
82107
}
83-
Button(onClick = {
84-
if (statusUiState.running)
85-
settingsViewModel.stopMPD()
86-
else
87-
settingsViewModel.startMPD(context)
88-
}) {
89-
Text(text = if (statusUiState.running) "Stop MPD" else "Start MPD")
108+
Button(
109+
onClick = {
110+
if (statusUiState.running)
111+
settingsViewModel.stopMPD()
112+
else
113+
settingsViewModel.startMPD(context)
114+
},
115+
enabled = settingsViewModel.mpdLoader.isLoaded
116+
&& storagePermissionState.status.isGranted
117+
) {
118+
Text(
119+
text = stringResource(id = if (statusUiState.running) R.string.stopMPD else R.string.startMPD)
120+
)
90121
}
91122
}
92123
Row(
@@ -139,4 +170,19 @@ fun AudioMediaPermission(storagePermissionState: PermissionState) {
139170
}
140171
}
141172
}
173+
}
174+
175+
@Composable
176+
fun MPDLoaderStatus(settingsViewModel: SettingsViewModel) {
177+
val loader = settingsViewModel.mpdLoader
178+
if (!loader.isLoaded) {
179+
val context = LocalContext.current
180+
SelectionContainer {
181+
Text(
182+
loader.loadFailureMessage(context),
183+
Modifier.padding(16.dp),
184+
color = MaterialTheme.colorScheme.error
185+
)
186+
}
187+
}
142188
}
Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
<?xml version="1.0" encoding="utf-8"?>
22

33
<resources>
4-
<string name="app_name">MPD</string>
5-
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
6-
<string name="notification_text_mpd_running">Touch for MPD options.</string>
7-
<string name="toggle_button_run_on">MPD is running</string>
8-
<string name="toggle_button_run_off">MPD is not running</string>
9-
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
10-
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
11-
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
12-
<string name="external_files_permission_request">MPD requires access to external files to play local music. Please grant the permission.</string>
13-
<string name="title_open_app_info">Open app info</string>
4+
<string name="app_name">MPD</string>
5+
<string name="notification_title_mpd_running">Music Player Daemon is running</string>
6+
<string name="notification_text_mpd_running">Touch for MPD options.</string>
7+
<string name="toggle_button_run_on">MPD is running</string>
8+
<string name="toggle_button_run_off">MPD is not running</string>
9+
<string name="checkbox_run_on_boot">Run MPD automatically on boot</string>
10+
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
11+
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
12+
<string name="external_files_permission_request">MPD requires access to external files to play local music. Please grant the permission.</string>
13+
<string name="title_open_app_info">Open app info</string>
14+
<string name="mpd_load_failure_message">"Failed to load the native MPD library.
15+
Report this problem to us, and include the following information:
16+
SUPPORTED_ABIS=%1$s
17+
PRODUCT=%2$s
18+
FINGERPRINT=%3$s
19+
error=%4$s"
20+
</string>
21+
<string name="stopped">Stopped</string>
22+
<string name="running">Running</string>
23+
<string name="stopMPD">Stop MPD</string>
24+
<string name="startMPD">Start MPD</string>
1425
</resources>
Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<resources>
3-
<style name="Theme.MPD" parent="android:Theme.Material.Light.NoActionBar" />
3+
<color name="red_500">#F44336</color>
4+
<color name="red_900">#B71C1C</color>
5+
<color name="green_300">#81C784</color>
6+
<color name="green_700">#388E3C</color>
7+
8+
<color name="colorErrorOnLight">@color/red_900</color>
9+
<color name="colorErrorOnDark">@color/red_500</color>
10+
11+
<color name="colorSuccessOnLight">@color/green_700</color>
12+
<color name="colorSuccessOnDark">@color/green_300</color>
13+
14+
<attr name="appColorNegative" format="color|reference" />
15+
<attr name="appColorPositive" format="color|reference" />
16+
17+
<style name="Theme.MPD" parent="android:Theme.Material.Light.NoActionBar">
18+
<item name="appColorNegative">@color/colorErrorOnLight</item>
19+
<item name="appColorPositive">@color/colorSuccessOnLight</item>
20+
</style>
421
</resources>

0 commit comments

Comments
 (0)