From 2efc1db6a928b4296700381e88ae799775f5c21f Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@gmail.com>
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 <jni.h>
 
 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 <max.kellermann@gmail.com>
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 <max.kellermann@gmail.com>
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 <max.kellermann@gmail.com>
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<jstring>(_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 <max.kellermann@gmail.com>
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 <max.kellermann@gmail.com>
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 <max.kellermann@gmail.com>
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 <max.kellermann@gmail.com>
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
 <sles_output>` 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 <max.kellermann@gmail.com>
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