From 2efc1db6a928b4296700381e88ae799775f5c21f Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 18:07:47 +0200 Subject: [PATCH 1/9] android/Environment: no namespace indent --- src/android/Environment.cxx | 22 ++++++++++++---------- src/android/Environment.hxx | 32 +++++++++++++++++--------------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/android/Environment.cxx b/src/android/Environment.cxx index 619acac19..9a5f51095 100644 --- a/src/android/Environment.cxx +++ b/src/android/Environment.cxx @@ -25,13 +25,13 @@ #include "fs/AllocatedPath.hxx" namespace Environment { - static Java::TrivialClass cls; - static jmethodID getExternalStorageDirectory_method; - static jmethodID getExternalStoragePublicDirectory_method; -} + +static Java::TrivialClass cls; +static jmethodID getExternalStorageDirectory_method; +static jmethodID getExternalStoragePublicDirectory_method; void -Environment::Initialise(JNIEnv *env) noexcept +Initialise(JNIEnv *env) noexcept { cls.Find(env, "android/os/Environment"); @@ -45,13 +45,13 @@ Environment::Initialise(JNIEnv *env) noexcept } void -Environment::Deinitialise(JNIEnv *env) noexcept +Deinitialise(JNIEnv *env) noexcept { cls.Clear(env); } AllocatedPath -Environment::getExternalStorageDirectory() noexcept +getExternalStorageDirectory() noexcept { JNIEnv *env = Java::GetEnv(); @@ -65,7 +65,7 @@ Environment::getExternalStorageDirectory() noexcept } AllocatedPath -Environment::getExternalStoragePublicDirectory(const char *type) noexcept +getExternalStoragePublicDirectory(const char *type) noexcept { if (getExternalStoragePublicDirectory_method == nullptr) /* needs API level 8 */ @@ -74,11 +74,13 @@ Environment::getExternalStoragePublicDirectory(const char *type) noexcept JNIEnv *env = Java::GetEnv(); Java::String type2(env, type); - jobject file = env->CallStaticObjectMethod(Environment::cls, - Environment::getExternalStoragePublicDirectory_method, + jobject file = env->CallStaticObjectMethod(cls, + getExternalStoragePublicDirectory_method, type2.Get()); if (file == nullptr) return nullptr; return Java::File::ToAbsolutePath(env, file); } + +} // namespace Environment diff --git a/src/android/Environment.hxx b/src/android/Environment.hxx index 15cc604cd..c0a5c0323 100644 --- a/src/android/Environment.hxx +++ b/src/android/Environment.hxx @@ -17,27 +17,29 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_ANDROID_ENVIRONMENT_HXX -#define MPD_ANDROID_ENVIRONMENT_HXX - -#include "util/Compiler.h" +#pragma once #include class AllocatedPath; namespace Environment { - void Initialise(JNIEnv *env) noexcept; - void Deinitialise(JNIEnv *env) noexcept; - /** - * Determine the mount point of the external SD card. - */ - [[gnu::pure]] - AllocatedPath getExternalStorageDirectory() noexcept; +void +Initialise(JNIEnv *env) noexcept; - [[gnu::pure]] - AllocatedPath getExternalStoragePublicDirectory(const char *type) noexcept; -} +void +Deinitialise(JNIEnv *env) noexcept; -#endif +/** + * Determine the mount point of the external SD card. + */ +[[gnu::pure]] +AllocatedPath +getExternalStorageDirectory() noexcept; + +[[gnu::pure]] +AllocatedPath +getExternalStoragePublicDirectory(const char *type) noexcept; + +} // namespace Environment From 1f4df2a64dc003973431a96dd0902d535ffce064 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 18:07:29 +0200 Subject: [PATCH 2/9] android/Environment: pass JNIEnv to all functions --- src/Main.cxx | 6 +++--- src/android/Environment.cxx | 8 ++------ src/android/Environment.hxx | 4 ++-- src/fs/StandardDirectory.cxx | 3 ++- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/Main.cxx b/src/Main.cxx index 77575a008..4e6d56412 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -591,12 +591,12 @@ MainConfigured(const CommandLineOptions &options, #ifdef ANDROID static void -AndroidMain() +AndroidMain(JNIEnv *env) { CommandLineOptions options; ConfigData raw_config; - const auto sdcard = Environment::getExternalStorageDirectory(); + const auto sdcard = Environment::getExternalStorageDirectory(env); if (!sdcard.IsNull()) { const auto config_path = sdcard / Path::FromFS("mpd.conf"); @@ -625,7 +625,7 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL AtScopeExit() { delete logListener; }; try { - AndroidMain(); + AndroidMain(env); } catch (...) { LogError(std::current_exception()); } diff --git a/src/android/Environment.cxx b/src/android/Environment.cxx index 9a5f51095..6075d6e73 100644 --- a/src/android/Environment.cxx +++ b/src/android/Environment.cxx @@ -51,10 +51,8 @@ Deinitialise(JNIEnv *env) noexcept } AllocatedPath -getExternalStorageDirectory() noexcept +getExternalStorageDirectory(JNIEnv *env) noexcept { - JNIEnv *env = Java::GetEnv(); - jobject file = env->CallStaticObjectMethod(cls, getExternalStorageDirectory_method); @@ -65,14 +63,12 @@ getExternalStorageDirectory() noexcept } AllocatedPath -getExternalStoragePublicDirectory(const char *type) noexcept +getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept { if (getExternalStoragePublicDirectory_method == nullptr) /* needs API level 8 */ return nullptr; - JNIEnv *env = Java::GetEnv(); - Java::String type2(env, type); jobject file = env->CallStaticObjectMethod(cls, getExternalStoragePublicDirectory_method, diff --git a/src/android/Environment.hxx b/src/android/Environment.hxx index c0a5c0323..9a089699a 100644 --- a/src/android/Environment.hxx +++ b/src/android/Environment.hxx @@ -36,10 +36,10 @@ Deinitialise(JNIEnv *env) noexcept; */ [[gnu::pure]] AllocatedPath -getExternalStorageDirectory() noexcept; +getExternalStorageDirectory(JNIEnv *env) noexcept; [[gnu::pure]] AllocatedPath -getExternalStoragePublicDirectory(const char *type) noexcept; +getExternalStoragePublicDirectory(JNIEnv *env, const char *type) noexcept; } // namespace Environment diff --git a/src/fs/StandardDirectory.cxx b/src/fs/StandardDirectory.cxx index 66785361e..2767d2c49 100644 --- a/src/fs/StandardDirectory.cxx +++ b/src/fs/StandardDirectory.cxx @@ -254,7 +254,8 @@ GetUserMusicDir() noexcept #elif defined(USE_XDG) return GetUserDir("XDG_MUSIC_DIR"); #elif defined(ANDROID) - return Environment::getExternalStoragePublicDirectory("Music"); + return Environment::getExternalStoragePublicDirectory(Java::GetEnv(), + "Music"); #else return nullptr; #endif From b90e32fe4e8c50de94af55f85f7684e50c76e314 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 15:12:18 +0200 Subject: [PATCH 3/9] Android/Context: look up methods once during startup --- src/Main.cxx | 3 +++ src/android/Context.cxx | 38 ++++++++++++++++++++------------------ src/android/Context.hxx | 6 ++++++ 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/Main.cxx b/src/Main.cxx index 4e6d56412..0228e8fba 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -614,9 +614,12 @@ Java_org_musicpd_Bridge_run(JNIEnv *env, jclass, jobject _context, jobject _logL Java::Init(env); Java::Object::Initialise(env); Java::File::Initialise(env); + Environment::Initialise(env); AtScopeExit(env) { Environment::Deinitialise(env); }; + Context::Initialise(env); + context = new Context(env, _context); AtScopeExit() { delete context; }; diff --git a/src/android/Context.cxx b/src/android/Context.cxx index 2c51779bd..f86f26bdf 100644 --- a/src/android/Context.cxx +++ b/src/android/Context.cxx @@ -26,19 +26,31 @@ #include "AudioManager.hxx" +static jmethodID getExternalFilesDir_method, + getCacheDir_method, + getSystemService_method; + +void +Context::Initialise(JNIEnv *env) noexcept +{ + Java::Class cls{env, "android/content/Context"}; + + getExternalFilesDir_method = env->GetMethodID(cls, "getExternalFilesDir", + "(Ljava/lang/String;)Ljava/io/File;"); + getCacheDir_method = env->GetMethodID(cls, "getCacheDir", + "()Ljava/io/File;"); + getSystemService_method = env->GetMethodID(cls, "getSystemService", + "(Ljava/lang/String;)Ljava/lang/Object;"); +} + AllocatedPath Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept { assert(_type != nullptr); - Java::Class cls{env, env->GetObjectClass(Get())}; - jmethodID method = env->GetMethodID(cls, "getExternalFilesDir", - "(Ljava/lang/String;)Ljava/io/File;"); - assert(method); - Java::String type{env, _type}; - jobject file = env->CallObjectMethod(Get(), method, type.Get()); + jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method, type.Get()); if (Java::DiscardException(env) || file == nullptr) return nullptr; @@ -50,12 +62,7 @@ Context::GetCacheDir(JNIEnv *env) const noexcept { assert(env != nullptr); - Java::Class cls(env, env->GetObjectClass(Get())); - jmethodID method = env->GetMethodID(cls, "getCacheDir", - "()Ljava/io/File;"); - assert(method); - - jobject file = env->CallObjectMethod(Get(), method); + jobject file = env->CallObjectMethod(Get(), getCacheDir_method); if (Java::DiscardException(env) || file == nullptr) return nullptr; @@ -67,13 +74,8 @@ Context::GetAudioManager(JNIEnv *env) noexcept { assert(env != nullptr); - Java::Class cls(env, env->GetObjectClass(Get())); - jmethodID method = env->GetMethodID(cls, "getSystemService", - "(Ljava/lang/String;)Ljava/lang/Object;"); - assert(method); - Java::String name(env, "audio"); - jobject am = env->CallObjectMethod(Get(), method, name.Get()); + jobject am = env->CallObjectMethod(Get(), getSystemService_method, name.Get()); if (Java::DiscardException(env) || am == nullptr) return nullptr; diff --git a/src/android/Context.hxx b/src/android/Context.hxx index da3d27c05..b771bc69a 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -27,6 +27,12 @@ class AudioManager; class Context : public Java::GlobalObject { public: + /** + * Global initialisation. Looks up the methods of the + * Context Java class. + */ + static void Initialise(JNIEnv *env) noexcept; + Context(JNIEnv *env, jobject obj) noexcept :Java::GlobalObject(env, obj) {} From 1aa3c1e543197ce34ceafeb407ea268d044f029a Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 16:41:28 +0200 Subject: [PATCH 4/9] java/String: add static method Optional() --- src/java/String.hxx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/java/String.hxx b/src/java/String.hxx index f60e7abc7..716268f02 100644 --- a/src/java/String.hxx +++ b/src/java/String.hxx @@ -89,6 +89,16 @@ public: String(JNIEnv *_env, const char *_value) noexcept :LocalRef(_env, _env->NewStringUTF(_value)) {} + /** + * This constructor allows passing a nullptr value, which maps + * to a "null" in Java. + */ + static String Optional(JNIEnv *_env, const char *_value) noexcept { + return _value != nullptr + ? String{_env, _value} + : String{}; + } + static StringUTFChars GetUTFChars(JNIEnv *env, jstring s) noexcept { return {env, s, env->GetStringUTFChars(s, nullptr)}; } From 5d0d5b5d97426b7234b57c431ccb05f58137a3c1 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 16:49:02 +0200 Subject: [PATCH 5/9] Android/Context: allow type=nullptr in GetExternalFilesDir() --- src/android/Context.cxx | 7 +++---- src/android/Context.hxx | 5 ++++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/android/Context.cxx b/src/android/Context.cxx index f86f26bdf..ebeda6ec4 100644 --- a/src/android/Context.cxx +++ b/src/android/Context.cxx @@ -44,13 +44,12 @@ Context::Initialise(JNIEnv *env) noexcept } AllocatedPath -Context::GetExternalFilesDir(JNIEnv *env, const char *_type) noexcept +Context::GetExternalFilesDir(JNIEnv *env, const char *type) noexcept { assert(_type != nullptr); - Java::String type{env, _type}; - - jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method, type.Get()); + jobject file = env->CallObjectMethod(Get(), getExternalFilesDir_method, + Java::String::Optional(env, type).Get()); if (Java::DiscardException(env) || file == nullptr) return nullptr; diff --git a/src/android/Context.hxx b/src/android/Context.hxx index b771bc69a..ae451e230 100644 --- a/src/android/Context.hxx +++ b/src/android/Context.hxx @@ -36,9 +36,12 @@ public: Context(JNIEnv *env, jobject obj) noexcept :Java::GlobalObject(env, obj) {} + /** + * @param type the subdirectory name; may be nullptr + */ [[gnu::pure]] AllocatedPath GetExternalFilesDir(JNIEnv *env, - const char *type) noexcept; + const char *type=nullptr) noexcept; [[gnu::pure]] AllocatedPath GetCacheDir(JNIEnv *env) const noexcept; From 6229210d5173b06590eedf4f2c8c7a301f63a872 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 14:59:50 +0200 Subject: [PATCH 6/9] Main: move code to LoadConfigFile() --- src/Main.cxx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/Main.cxx b/src/Main.cxx index 0228e8fba..895a96d2c 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -590,19 +590,25 @@ MainConfigured(const CommandLineOptions &options, #ifdef ANDROID +static void +LoadConfigFile(JNIEnv *env, ConfigData &config) +{ + if (const auto dir = Environment::getExternalStorageDirectory(env); + !dir.IsNull()) { + const auto config_path = + dir / Path::FromFS("mpd.conf"); + if (FileExists(config_path)) + ReadConfigFile(config, config_path); + } +} + static void AndroidMain(JNIEnv *env) { CommandLineOptions options; ConfigData raw_config; - const auto sdcard = Environment::getExternalStorageDirectory(env); - if (!sdcard.IsNull()) { - const auto config_path = - sdcard / Path::FromFS("mpd.conf"); - if (FileExists(config_path)) - ReadConfigFile(raw_config, config_path); - } + LoadConfigFile(env, raw_config); MainConfigured(options, raw_config); } From 7778210269efc6957d42a588f89eeb68bae91357 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 17:59:01 +0200 Subject: [PATCH 7/9] Main: move code to TryReadConfigFile() --- src/Main.cxx | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Main.cxx b/src/Main.cxx index 895a96d2c..e764baed3 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -590,16 +590,26 @@ MainConfigured(const CommandLineOptions &options, #ifdef ANDROID +/** + * Wrapper for ReadConfigFile() which returns false if the file was + * not found. + */ +static bool +TryReadConfigFile(ConfigData &config, Path path) +{ + if (!FileExists(path)) + return false; + + ReadConfigFile(config, path); + return true; +} + static void LoadConfigFile(JNIEnv *env, ConfigData &config) { if (const auto dir = Environment::getExternalStorageDirectory(env); - !dir.IsNull()) { - const auto config_path = - dir / Path::FromFS("mpd.conf"); - if (FileExists(config_path)) - ReadConfigFile(config, config_path); - } + !dir.IsNull()) + TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")); } static void From 40bc60d6ae7c722516d357dcb59d98c72bf836bf Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 18:05:31 +0200 Subject: [PATCH 8/9] Main: load Android mpd.conf from ExternalFilesDir See also https://github.com/MusicPlayerDaemon/MPD/issues/1061 Closes https://github.com/MusicPlayerDaemon/MPD/issues/1570 --- NEWS | 2 ++ doc/user.rst | 4 +++- src/Main.cxx | 11 +++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 4c36c9b5d..5c4a394ec 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ ver 0.23.9 (not yet released) * fix bogus volume levels with multiple partitions * improve iconv detection * macOS: fix macOS 10 build problem (0.23.8 regression) +* Android + - load mpd.conf from app data directory ver 0.23.8 (2022/07/09) * storage diff --git a/doc/user.rst b/doc/user.rst index 85b8ba696..b2831bab4 100644 --- a/doc/user.rst +++ b/doc/user.rst @@ -36,7 +36,9 @@ Installing on Android An experimental Android build is available on Google Play. After installing and launching it, :program:`MPD` will scan the music in your Music directory and you can control it as usual with a :program:`MPD` client. -If you need to tweak the configuration, you can create a file called :file:`mpd.conf` on the data partition (the directory which is returned by Android's :dfn:`getExternalStorageDirectory()` API function). +If you need to tweak the configuration, you can create a file called +:file:`mpd.conf` in MPD's data directory on the external storage +(usually :file:`Android/data/org.musicpd/files/mpd.conf`). ALSA is not available on Android; only the :ref:`OpenSL ES ` output plugin can be used for local playback. diff --git a/src/Main.cxx b/src/Main.cxx index e764baed3..bcbda0bb5 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -607,6 +607,17 @@ TryReadConfigFile(ConfigData &config, Path path) static void LoadConfigFile(JNIEnv *env, ConfigData &config) { + /* try loading mpd.conf from + "Android/data/org.musicpd/files/mpd.conf" (the app specific + data directory) first */ + if (const auto dir = context->GetExternalFilesDir(env); + !dir.IsNull() && + TryReadConfigFile(config, dir / Path::FromFS("mpd.conf"))) + return; + + /* if that fails, attempt to load "mpd.conf" from the root of + the SD card (pre-0.23.9, ceases to work since Android + 12) */ if (const auto dir = Environment::getExternalStorageDirectory(env); !dir.IsNull()) TryReadConfigFile(config, dir / Path::FromFS("mpd.conf")); From 12147f6d5822899cc4316799b494c093b4b47f91 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 18 Aug 2022 18:20:54 +0200 Subject: [PATCH 9/9] release v0.23.9 --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 5c4a394ec..38f41db10 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -ver 0.23.9 (not yet released) +ver 0.23.9 (2022/08/18) * input - cdio_paranoia: add options "mode" and "skip" * decoder