[Base] Android content URI file descriptor opening
This commit is contained in:
parent
34a952d789
commit
93a7918025
|
@ -14,8 +14,10 @@
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -122,6 +124,14 @@ struct FileInfo {
|
||||||
bool GetInfo(const std::filesystem::path& path, FileInfo* out_info);
|
bool GetInfo(const std::filesystem::path& path, FileInfo* out_info);
|
||||||
std::vector<FileInfo> ListFiles(const std::filesystem::path& path);
|
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 filesystem
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/cvar.h"
|
#include "xenia/base/cvar.h"
|
||||||
|
#include "xenia/base/filesystem.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/memory.h"
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/system.h"
|
#include "xenia/base/system.h"
|
||||||
|
@ -98,6 +99,8 @@ void InitializeAndroidAppFromMainThread(int32_t api_level,
|
||||||
// Logging uses threading.
|
// Logging uses threading.
|
||||||
xe::threading::AndroidInitialize();
|
xe::threading::AndroidInitialize();
|
||||||
|
|
||||||
|
xe::filesystem::AndroidInitialize();
|
||||||
|
|
||||||
// Initialize the cvars before logging.
|
// Initialize the cvars before logging.
|
||||||
if (launch_arguments_bundle) {
|
if (launch_arguments_bundle) {
|
||||||
cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle);
|
cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle);
|
||||||
|
@ -135,6 +138,8 @@ void ShutdownAndroidAppFromMainThread() {
|
||||||
|
|
||||||
xe::ShutdownLogging();
|
xe::ShutdownLogging();
|
||||||
|
|
||||||
|
xe::filesystem::AndroidShutdown();
|
||||||
|
|
||||||
xe::threading::AndroidShutdown();
|
xe::threading::AndroidShutdown();
|
||||||
|
|
||||||
if (android_application_context_) {
|
if (android_application_context_) {
|
||||||
|
|
Loading…
Reference in New Issue