/** ****************************************************************************** * 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 #include #include #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(android_parcel_file_descriptor_class_)); } if (android_content_resolver_class_) { jni_env->DeleteGlobalRef( reinterpret_cast(android_content_resolver_class_)); } if (android_content_resolver_) { jni_env->DeleteGlobalRef( reinterpret_cast(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(context_class)); AndroidShutdownContentResolver(); return; } jobject content_resolver = jni_env->CallObjectMethod( application_context, context_get_content_resolver); jni_env->DeleteLocalRef(reinterpret_cast(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(jni_env->NewGlobalRef( reinterpret_cast(content_resolver_class))); jni_env->DeleteLocalRef(reinterpret_cast(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(jni_env->NewGlobalRef( reinterpret_cast(parcel_file_descriptor_class))); jni_env->DeleteLocalRef( reinterpret_cast(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( jni_env->NewGlobalRef(reinterpret_cast(uri_class))); jni_env->DeleteLocalRef(reinterpret_cast(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 constexpr 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(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; } bool SetAttributes(const std::filesystem::path& path, uint64_t attributes) { return false; } } // namespace filesystem } // namespace xe