Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android updates #2206

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
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
20 changes: 14 additions & 6 deletions android/.gitignore
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
## ignoring .idea completely
# until a good reason emerges not to
/.idea
##--
## moved the following into .idea/.gitignore
#/.idea/caches
#/.idea/libraries
#/.idea/modules.xml
#/.idea/workspace.xml
#/.idea/navEditor.xml
#/.idea/assetWizardSettings.xml
## --
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
# both were used: different spelling
app/src/main/jnilibs/
app/src/main/jniLibs/
32 changes: 32 additions & 0 deletions android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

Notes and resources for MPD android maintainers.

## Build

See [Compiling for Android](https://github.com/MusicPlayerDaemon/MPD/blob/45cb098cd765af12316f8dca5635ef10a852e013/doc/user.rst#compiling-for-android)

## Android studio

### Version control

git ignoring .idea directory completely until a good reason emerges not to

* [How to manage projects under Version Control Systems (jetbrains.com)](https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems)

* [gradle.xml should work like workspace.xml? (jetbrains.com)](https://youtrack.jetbrains.com/issue/IDEA-55923)
Expand All @@ -14,4 +20,30 @@ Notes and resources for MPD android maintainers.

* [Include prebuilt native libraries (developer.android.com)](https://developer.android.com/studio/projects/gradle-external-native-builds#jniLibs)

## Permissions

### Files access

The required permission depends on android SDK version:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
Manifest.permission.READ_MEDIA_AUDIO
else
Manifest.permission.READ_EXTERNAL_STORAGE

### Permission request

[Request runtime permissions](https://developer.android.com/training/permissions/requesting)

Since Android 6.0 (API level 23):

Android will ignore permission request and will not show the request dialog
if the user's action implies "don't ask again."
This leaves the app in a crippled state and the user confused.
Google says "don't try to convince the user", so it returns false for `shouldShowRequestPermissionRationale`.

To help the user proceed, we show the `Request permission` button only if `shouldShowRequestPermissionRationale == true`
because there's a good chance the permission request dialog will not be ignored.

If `shouldShowRequestPermissionRationale == false` we instead show the "rationale" message and a button to open
the app info dialog where the user can explicitly grand the permission.
96 changes: 67 additions & 29 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.dagger.hilt.android)
}

android {
Expand All @@ -18,11 +18,6 @@ android {
vectorDrawables {
useSupportLibrary = true
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your app.
abiFilters += "arm64-v8a"
}
}

buildFeatures {
Expand All @@ -31,7 +26,7 @@ android {
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.7"
kotlinCompilerExtensionVersion = "1.5.10"
}

buildTypes {
Expand All @@ -46,12 +41,55 @@ android {
)
}
}
// flavors
flavorDimensions += "base"
productFlavors {
create("fail-test") {
// To test System.loadLibrary("mpd") failure
// exclude the native lib from the package
packaging {
jniLibs {
// it appears the 'excludes' is applied to all flavors
// even if it's only inside this flavor.
// this filters by task name to apply the exclusion only
// for this flavor name.
// (clearing the 'abiFilters' will only create a universal apk
// with all of the abi versions)
gradle.startParameter.getTaskNames().forEach { task ->
if (task.contains("fail-test", ignoreCase = true)) {
println("NOTICE: excluding libmpd.so from package $task for testing")
excludes += "**/libmpd.so"
}
}
}
}
}
create("arm64-v8a") {
ndk {
// ABI to include in package
//noinspection ChromeOsAbiSupport
abiFilters += listOf("arm64-v8a")
}
}
create("x86_64") {
ndk {
// ABI to include in package
abiFilters += listOf("x86_64")
}
}
create("universal") {
ndk {
// ABI to include in package
abiFilters += listOf("arm64-v8a", "x86_64")
}
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_9
targetCompatibility = JavaVersion.VERSION_1_9
}
kotlinOptions {
jvmTarget = "9"
jvmTarget = JavaVersion.VERSION_1_9.toString()
}
packaging {
resources {
Expand All @@ -61,30 +99,30 @@ android {
}

dependencies {
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
implementation(platform("androidx.compose:compose-bom:2025.01.01"))
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(platform(libs.androidx.compose.bom))

implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.10.0")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
implementation("androidx.navigation:navigation-compose:2.8.6")
implementation(libs.androidx.material3)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)

implementation("com.github.alorma:compose-settings-ui-m3:1.0.3")
implementation("com.github.alorma:compose-settings-storage-preferences:1.0.3")
implementation("com.google.accompanist:accompanist-permissions:0.33.2-alpha")
implementation(libs.compose.settings.ui.m3)
implementation(libs.compose.settings.storage.preferences)
implementation(libs.accompanist.permissions)

implementation("com.google.dagger:hilt-android:2.49")
ksp("com.google.dagger:dagger-compiler:2.49")
ksp("com.google.dagger:hilt-compiler:2.49")
implementation(libs.hilt.android)
ksp(libs.dagger.compiler)
ksp(libs.hilt.compiler)

implementation("androidx.media3:media3-session:1.5.1")
implementation(libs.androidx.media3.session)

// Android Studio Preview support
implementation("androidx.compose.ui:ui-tooling-preview")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
implementation(libs.androidx.ui.tooling.preview)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

implementation("androidx.appcompat:appcompat:1.7.0")
implementation(libs.androidx.appcompat)
}
4 changes: 3 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
android:required="false" />

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
Expand Down
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
)
}
}
Loading