From ef605f7d8f8241b95b977d95cf5247c1f2d8a309 Mon Sep 17 00:00:00 2001 From: bunnei Date: Fri, 3 Feb 2023 16:13:16 -0800 Subject: android: Implement SAF support & migrate to SDK 31. (#4) --- src/common/CMakeLists.txt | 8 ++++ src/common/fs/file.cpp | 38 +++++++++++++++++ src/common/fs/fs_android.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++ src/common/fs/fs_android.h | 62 ++++++++++++++++++++++++++++ src/common/fs/path_util.cpp | 31 ++++++++++---- src/common/fs/path_util.h | 8 ++++ 6 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 src/common/fs/fs_android.cpp create mode 100644 src/common/fs/fs_android.h (limited to 'src/common') diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 13ed68b3f..aecb46872 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -155,6 +155,14 @@ if (WIN32) target_link_libraries(common PRIVATE ntdll) endif() +if(ANDROID) + target_sources(common + PRIVATE + fs/fs_android.cpp + fs/fs_android.h + ) +endif() + if(ARCHITECTURE_x86_64) target_sources(common PRIVATE diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 656b03cc5..b0b25eb43 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp @@ -5,6 +5,9 @@ #include "common/fs/file.h" #include "common/fs/fs.h" +#ifdef ANDROID +#include "common/fs/fs_android.h" +#endif #include "common/logging/log.h" #ifdef _WIN32 @@ -252,6 +255,23 @@ void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, File } else { _wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type)); } +#elif ANDROID + if (Android::IsContentUri(path)) { + ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!"); + const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read); + if (fd != -1) { + file = fdopen(fd, "r"); + const auto error_num = errno; + if (error_num != 0 && file == nullptr) { + LOG_ERROR(Common_Filesystem, "Error opening file: {}, error: {}", path.c_str(), + strerror(error_num)); + } + } else { + LOG_ERROR(Common_Filesystem, "Error opening file: {}", path.c_str()); + } + } else { + file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); + } #else file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); #endif @@ -372,6 +392,23 @@ u64 IOFile::GetSize() const { // Flush any unwritten buffered data into the file prior to retrieving the file size. std::fflush(file); +#if ANDROID + u64 file_size = 0; + if (Android::IsContentUri(file_path)) { + file_size = Android::GetSize(file_path); + } else { + std::error_code ec; + + file_size = fs::file_size(file_path, ec); + + if (ec) { + LOG_ERROR(Common_Filesystem, + "Failed to retrieve the file size of path={}, ec_message={}", + PathToUTF8String(file_path), ec.message()); + return 0; + } + } +#else std::error_code ec; const auto file_size = fs::file_size(file_path, ec); @@ -381,6 +418,7 @@ u64 IOFile::GetSize() const { PathToUTF8String(file_path), ec.message()); return 0; } +#endif return file_size; } diff --git a/src/common/fs/fs_android.cpp b/src/common/fs/fs_android.cpp new file mode 100644 index 000000000..298a79bac --- /dev/null +++ b/src/common/fs/fs_android.cpp @@ -0,0 +1,98 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "common/fs/fs_android.h" + +namespace Common::FS::Android { + +JNIEnv* GetEnvForThread() { + thread_local static struct OwnedEnv { + OwnedEnv() { + status = g_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (status == JNI_EDETACHED) + g_jvm->AttachCurrentThread(&env, nullptr); + } + + ~OwnedEnv() { + if (status == JNI_EDETACHED) + g_jvm->DetachCurrentThread(); + } + + int status; + JNIEnv* env = nullptr; + } owned; + return owned.env; +} + +void RegisterCallbacks(JNIEnv* env, jclass clazz) { + env->GetJavaVM(&g_jvm); + native_library = clazz; + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ + F(JMethodID, JMethodName, Signature) +#define F(JMethodID, JMethodName, Signature) \ + JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature); + ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) + ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR +} + +void UnRegisterCallbacks() { +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) +#define F(JMethodID) JMethodID = nullptr; + ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) + ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR +} + +bool IsContentUri(const std::string& path) { + constexpr std::string_view prefix = "content://"; + if (path.size() < prefix.size()) [[unlikely]] { + return false; + } + + return path.find(prefix) == 0; +} + +int OpenContentUri(const std::string& filepath, OpenMode openmode) { + if (open_content_uri == nullptr) + return -1; + + const char* mode = ""; + switch (openmode) { + case OpenMode::Read: + mode = "r"; + break; + default: + UNIMPLEMENTED(); + return -1; + } + auto env = GetEnvForThread(); + jstring j_filepath = env->NewStringUTF(filepath.c_str()); + jstring j_mode = env->NewStringUTF(mode); + return env->CallStaticIntMethod(native_library, open_content_uri, j_filepath, j_mode); +} + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, ReturnValue, JMethodID, Caller) +#define F(FunctionName, ReturnValue, JMethodID, Caller) \ + ReturnValue FunctionName(const std::string& filepath) { \ + if (JMethodID == nullptr) { \ + return 0; \ + } \ + auto env = GetEnvForThread(); \ + jstring j_filepath = env->NewStringUTF(filepath.c_str()); \ + return env->Caller(native_library, JMethodID, j_filepath); \ + } +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +#undef F +#undef FR + +} // namespace Common::FS::Android diff --git a/src/common/fs/fs_android.h b/src/common/fs/fs_android.h new file mode 100644 index 000000000..bb8a52648 --- /dev/null +++ b/src/common/fs/fs_android.h @@ -0,0 +1,62 @@ +// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#define ANDROID_STORAGE_FUNCTIONS(V) \ + V(OpenContentUri, int, (const std::string& filepath, OpenMode openmode), open_content_uri, \ + "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") + +#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ + V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") + +namespace Common::FS::Android { + +static JavaVM* g_jvm = nullptr; +static jclass native_library = nullptr; + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID) +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID) +#define F(JMethodID) static jmethodID JMethodID = nullptr; +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS +#undef FR + +enum class OpenMode { + Read, + Write, + ReadWrite, + WriteAppend, + WriteTruncate, + ReadWriteAppend, + ReadWriteTruncate, + Never +}; + +void RegisterCallbacks(JNIEnv* env, jclass clazz); + +void UnRegisterCallbacks(); + +bool IsContentUri(const std::string& path); + +#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \ + F(FunctionName, Parameters, ReturnValue) +#define F(FunctionName, Parameters, ReturnValue) ReturnValue FunctionName Parameters; +ANDROID_STORAGE_FUNCTIONS(FS) +#undef F +#undef FS + +#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ + F(FunctionName, ReturnValue) +#define F(FunctionName, ReturnValue) ReturnValue FunctionName(const std::string& filepath); +ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR) +#undef F +#undef FR + +} // namespace Common::FS::Android diff --git a/src/common/fs/path_util.cpp b/src/common/fs/path_util.cpp index ca755b053..e026a13d9 100644 --- a/src/common/fs/path_util.cpp +++ b/src/common/fs/path_util.cpp @@ -6,6 +6,9 @@ #include #include "common/fs/fs.h" +#ifdef ANDROID +#include "common/fs/fs_android.h" +#endif #include "common/fs/fs_paths.h" #include "common/fs/path_util.h" #include "common/logging/log.h" @@ -80,9 +83,7 @@ public: yuzu_paths.insert_or_assign(yuzu_path, new_path); } -private: - PathManagerImpl() { - fs::path yuzu_path; + void Reinitialize(fs::path yuzu_path = {}) { fs::path yuzu_path_cache; fs::path yuzu_path_config; @@ -96,12 +97,9 @@ private: yuzu_path_cache = yuzu_path / CACHE_DIR; yuzu_path_config = yuzu_path / CONFIG_DIR; #elif ANDROID - // On Android internal storage is mounted as "/sdcard" - if (Exists("/sdcard")) { - yuzu_path = "/sdcard/yuzu-emu"; - yuzu_path_cache = yuzu_path / CACHE_DIR; - yuzu_path_config = yuzu_path / CONFIG_DIR; - } + ASSERT(!yuzu_path.empty()); + yuzu_path_cache = yuzu_path / CACHE_DIR; + yuzu_path_config = yuzu_path / CONFIG_DIR; #else yuzu_path = GetCurrentDir() / PORTABLE_DIR; @@ -129,6 +127,11 @@ private: GenerateYuzuPath(YuzuPath::TASDir, yuzu_path / TAS_DIR); } +private: + PathManagerImpl() { + Reinitialize(); + } + ~PathManagerImpl() = default; void GenerateYuzuPath(YuzuPath yuzu_path, const fs::path& new_path) { @@ -217,6 +220,10 @@ fs::path RemoveTrailingSeparators(const fs::path& path) { return fs::path{string_path}; } +void SetAppDirectory(const std::string& app_directory) { + PathManagerImpl::GetInstance().Reinitialize(app_directory); +} + const fs::path& GetYuzuPath(YuzuPath yuzu_path) { return PathManagerImpl::GetInstance().GetYuzuPathImpl(yuzu_path); } @@ -357,6 +364,12 @@ std::vector SplitPathComponents(std::string_view filename) { std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) { std::string path(path_); +#ifdef ANDROID + if (Android::IsContentUri(path)) { + return path; + } +#endif // ANDROID + char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\'; char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/'; diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 13d713f1e..7cfe85b70 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h @@ -180,6 +180,14 @@ template } #endif +/** + * Sets the directory used for application storage. Used on Android where we do not know internal + * storage until informed by the frontend. + * + * @param app_directory Directory to use for application storage. + */ +void SetAppDirectory(const std::string& app_directory); + /** * Gets the filesystem path associated with the YuzuPath enum. * -- cgit v1.2.3