[Base] Android content URI file descriptor opening

This commit is contained in:
Triang3l 2022-07-17 16:25:58 +03:00
parent 34a952d789
commit 93a7918025
3 changed files with 293 additions and 0 deletions

View File

@ -14,8 +14,10 @@
#include <iterator>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include "xenia/base/platform.h"
#include "xenia/base/string.h"
namespace xe {
@ -122,6 +124,14 @@ struct FileInfo {
bool GetInfo(const std::filesystem::path& path, FileInfo* out_info);
std::vector<FileInfo> ListFiles(const std::filesystem::path& path);
#if XE_PLATFORM_ANDROID
void AndroidInitialize();
void AndroidShutdown();
bool IsAndroidContentUri(const std::string_view source);
int OpenAndroidContentFileDescriptor(const std::string_view uri,
const char* mode);
#endif // XE_PLATFORM_ANDROID
} // namespace filesystem
} // namespace xe

View File

@ -0,0 +1,278 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2022 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include <android/log.h>
#include <jni.h>
#include <cstring>
#include "xenia/base/filesystem.h"
#include "xenia/base/main_android.h"
#include "xenia/base/math.h"
#include "xenia/base/string.h"
namespace xe {
namespace filesystem {
// Using Android logging because the file system may need to be initialized
// before logging.
static jobject android_content_resolver_;
static jclass android_content_resolver_class_;
static jmethodID android_content_resolver_open_file_descriptor_;
static jclass android_parcel_file_descriptor_class_;
static jmethodID android_parcel_file_descriptor_detach_fd_;
static jclass android_uri_class_;
static jmethodID android_uri_parse_;
static bool android_content_resolver_initialized_;
static void AndroidShutdownContentResolver() {
android_content_resolver_initialized_ = false;
android_uri_parse_ = nullptr;
android_parcel_file_descriptor_detach_fd_ = nullptr;
android_content_resolver_open_file_descriptor_ = nullptr;
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (jni_env) {
if (android_uri_class_) {
jni_env->DeleteGlobalRef(android_uri_class_);
}
if (android_parcel_file_descriptor_class_) {
jni_env->DeleteGlobalRef(
reinterpret_cast<jobject>(android_parcel_file_descriptor_class_));
}
if (android_content_resolver_class_) {
jni_env->DeleteGlobalRef(
reinterpret_cast<jobject>(android_content_resolver_class_));
}
if (android_content_resolver_) {
jni_env->DeleteGlobalRef(
reinterpret_cast<jobject>(android_content_resolver_));
}
}
android_uri_class_ = nullptr;
android_parcel_file_descriptor_class_ = nullptr;
android_content_resolver_class_ = nullptr;
android_content_resolver_ = nullptr;
}
static void AndroidInitializeContentResolver() {
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (!jni_env) {
return;
}
jobject application_context = GetAndroidApplicationContext();
if (!application_context) {
return;
}
{
jclass context_class = jni_env->GetObjectClass(application_context);
if (!context_class) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the context class");
AndroidShutdownContentResolver();
return;
}
jmethodID context_get_content_resolver =
jni_env->GetMethodID(context_class, "getContentResolver",
"()Landroid/content/ContentResolver;");
if (!context_get_content_resolver) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the getContentResolver method of the context");
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(context_class));
AndroidShutdownContentResolver();
return;
}
jobject content_resolver = jni_env->CallObjectMethod(
application_context, context_get_content_resolver);
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(context_class));
if (!content_resolver) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the content resolver");
AndroidShutdownContentResolver();
return;
}
android_content_resolver_ = jni_env->NewGlobalRef(content_resolver);
jni_env->DeleteLocalRef(content_resolver);
}
if (!android_content_resolver_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to create a global reference to the content resolver");
AndroidShutdownContentResolver();
return;
}
{
jobject content_resolver_class =
jni_env->GetObjectClass(android_content_resolver_);
if (!content_resolver_class) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the content resolver class");
AndroidShutdownContentResolver();
return;
}
android_content_resolver_class_ =
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
reinterpret_cast<jobject>(content_resolver_class)));
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(content_resolver_class));
}
if (!android_content_resolver_class_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to create a global reference to the content resolver class");
AndroidShutdownContentResolver();
return;
}
android_content_resolver_open_file_descriptor_ = jni_env->GetMethodID(
android_content_resolver_class_, "openFileDescriptor",
"(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;");
if (!android_content_resolver_open_file_descriptor_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the openFileDescriptor method of the content resolver");
AndroidShutdownContentResolver();
return;
}
{
jclass parcel_file_descriptor_class =
jni_env->FindClass("android/os/ParcelFileDescriptor");
if (!parcel_file_descriptor_class) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the parcel file descriptor class");
AndroidShutdownContentResolver();
return;
}
android_parcel_file_descriptor_class_ =
reinterpret_cast<jclass>(jni_env->NewGlobalRef(
reinterpret_cast<jobject>(parcel_file_descriptor_class)));
jni_env->DeleteLocalRef(
reinterpret_cast<jobject>(parcel_file_descriptor_class));
}
if (!android_parcel_file_descriptor_class_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to create a global reference to the parcel file descriptor "
"class");
AndroidShutdownContentResolver();
return;
}
android_parcel_file_descriptor_detach_fd_ = jni_env->GetMethodID(
android_parcel_file_descriptor_class_, "detachFd", "()I");
if (!android_parcel_file_descriptor_detach_fd_) {
__android_log_write(
ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the detachFd method of the parcel file descriptor");
AndroidShutdownContentResolver();
return;
}
{
jclass uri_class = jni_env->FindClass("android/net/Uri");
if (!uri_class) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the URI class");
AndroidShutdownContentResolver();
return;
}
android_uri_class_ = reinterpret_cast<jclass>(
jni_env->NewGlobalRef(reinterpret_cast<jobject>(uri_class)));
jni_env->DeleteLocalRef(reinterpret_cast<jobject>(uri_class));
}
if (!android_uri_class_) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to create a global reference to the URI class");
AndroidShutdownContentResolver();
return;
}
android_uri_parse_ = jni_env->GetStaticMethodID(
android_uri_class_, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
if (!android_uri_parse_) {
__android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver",
"Failed to get the parse method of the URI class");
AndroidShutdownContentResolver();
return;
}
android_content_resolver_initialized_ = true;
}
void AndroidInitialize() { AndroidInitializeContentResolver(); }
void AndroidShutdown() { AndroidShutdownContentResolver(); }
bool IsAndroidContentUri(const std::string_view source) {
// A URI schema is case-insensitive. Though just content: defines the schema,
// still including // in the comparison to distinguish from a file with a name
// starting from content: (as this is the main purpose of this code -
// separating URIs from file paths) more clearly.
static const char kContentSchema[] = "content://";
constexpr size_t kContentSchemaLength = xe::countof(kContentSchema) - 1;
return source.size() >= kContentSchemaLength &&
!xe_strncasecmp(source.data(), kContentSchema, kContentSchemaLength);
}
int OpenAndroidContentFileDescriptor(const std::string_view uri,
const char* mode) {
if (!android_content_resolver_initialized_) {
return -1;
}
JNIEnv* jni_env = GetAndroidThreadJniEnv();
if (!jni_env) {
return -1;
}
jobject uri_object;
{
jstring uri_string;
{
std::u16string uri_u16 = xe::to_utf16(uri);
uri_string = jni_env->NewString(
reinterpret_cast<const jchar*>(uri_u16.data()), uri_u16.size());
}
if (!uri_string) {
return -1;
}
uri_object = jni_env->CallStaticObjectMethod(
android_uri_class_, android_uri_parse_, uri_string);
jni_env->DeleteLocalRef(uri_string);
}
if (!uri_object) {
return -1;
}
jstring mode_string = jni_env->NewStringUTF(mode);
if (!mode_string) {
jni_env->DeleteLocalRef(uri_object);
return -1;
}
jobject parcel_file_descriptor = jni_env->CallObjectMethod(
android_content_resolver_, android_content_resolver_open_file_descriptor_,
uri_object, mode_string);
jni_env->DeleteLocalRef(mode_string);
jni_env->DeleteLocalRef(uri_object);
if (jni_env->ExceptionCheck()) {
jni_env->ExceptionClear();
return -1;
}
if (!parcel_file_descriptor) {
return -1;
}
int file_descriptor = jni_env->CallIntMethod(
parcel_file_descriptor, android_parcel_file_descriptor_detach_fd_);
jni_env->DeleteLocalRef(parcel_file_descriptor);
return file_descriptor;
}
} // namespace filesystem
} // namespace xe

View File

@ -16,6 +16,7 @@
#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/filesystem.h"
#include "xenia/base/logging.h"
#include "xenia/base/memory.h"
#include "xenia/base/system.h"
@ -98,6 +99,8 @@ void InitializeAndroidAppFromMainThread(int32_t api_level,
// Logging uses threading.
xe::threading::AndroidInitialize();
xe::filesystem::AndroidInitialize();
// Initialize the cvars before logging.
if (launch_arguments_bundle) {
cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle);
@ -135,6 +138,8 @@ void ShutdownAndroidAppFromMainThread() {
xe::ShutdownLogging();
xe::filesystem::AndroidShutdown();
xe::threading::AndroidShutdown();
if (android_application_context_) {