android: Implement SAF support & migrate to SDK 31. (#4)

This commit is contained in:
bunnei 2023-02-03 16:13:16 -08:00
parent 39ab81a098
commit ef605f7d8f
38 changed files with 856 additions and 702 deletions

View file

@ -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

View file

@ -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;
}

View file

@ -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<void**>(&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

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#include <jni.h>
#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

View file

@ -6,6 +6,9 @@
#include <unordered_map>
#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<std::string> 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 ? '\\' : '/';

View file

@ -180,6 +180,14 @@ template <typename Path>
}
#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.
*