From 6e1e6d2311df787e800be199df369b5d12aa6cae Mon Sep 17 00:00:00 2001 From: JosJuice Date: Fri, 26 Jun 2020 17:52:31 +0200 Subject: [PATCH] Android: Add content provider support to File::IOFile Taking the hit now to prepare us for when Google Play will force us to use scoped storage... --- .../dolphinemu/utils/ContentHandler.java | 23 +++++++++++++++++++ .../jni/AndroidCommon/AndroidCommon.cpp | 18 +++++++++++++++ .../Android/jni/AndroidCommon/AndroidCommon.h | 2 ++ .../Android/jni/AndroidCommon/CMakeLists.txt | 15 ++++++++++++ Source/Android/jni/AndroidCommon/IDCache.cpp | 20 ++++++++++++++++ Source/Android/jni/AndroidCommon/IDCache.h | 3 +++ Source/Android/jni/CMakeLists.txt | 6 +++-- Source/CMakeLists.txt | 4 ++-- Source/Core/Common/CMakeLists.txt | 5 ++++ Source/Core/Common/File.cpp | 23 ++++++++++++++++++- 10 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java create mode 100644 Source/Android/jni/AndroidCommon/CMakeLists.txt diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java new file mode 100644 index 0000000000..a42c464d52 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/utils/ContentHandler.java @@ -0,0 +1,23 @@ +package org.dolphinemu.dolphinemu.utils; + +import android.net.Uri; + +import org.dolphinemu.dolphinemu.DolphinApplication; + +import java.io.FileNotFoundException; + +public class ContentHandler +{ + public static int openFd(String uri, String mode) + { + try + { + return DolphinApplication.getAppContext().getContentResolver() + .openFileDescriptor(Uri.parse(uri), mode).detachFd(); + } + catch (FileNotFoundException | NullPointerException e) + { + return -1; + } + } +} diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp index 1f9973d451..c8312048e9 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.cpp +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.cpp @@ -11,6 +11,7 @@ #include #include "Common/StringUtil.h" +#include "jni/AndroidCommon/IDCache.h" std::string GetJString(JNIEnv* env, jstring jstr) { @@ -40,3 +41,20 @@ std::vector JStringArrayToVector(JNIEnv* env, jobjectArray array) return result; } + +int OpenAndroidContent(const std::string& uri, const std::string& mode) +{ + JNIEnv* env = IDCache::GetEnvForThread(); + const jint fd = env->CallStaticIntMethod(IDCache::GetContentHandlerClass(), + IDCache::GetContentHandlerOpenFd(), ToJString(env, uri), + ToJString(env, mode)); + + // We can get an IllegalArgumentException when passing an invalid mode + if (env->ExceptionCheck()) + { + env->ExceptionDescribe(); + abort(); + } + + return fd; +} diff --git a/Source/Android/jni/AndroidCommon/AndroidCommon.h b/Source/Android/jni/AndroidCommon/AndroidCommon.h index e60678a3e8..4940844564 100644 --- a/Source/Android/jni/AndroidCommon/AndroidCommon.h +++ b/Source/Android/jni/AndroidCommon/AndroidCommon.h @@ -11,3 +11,5 @@ std::string GetJString(JNIEnv* env, jstring jstr); jstring ToJString(JNIEnv* env, const std::string& str); std::vector JStringArrayToVector(JNIEnv* env, jobjectArray array); + +int OpenAndroidContent(const std::string& uri, const std::string& mode); diff --git a/Source/Android/jni/AndroidCommon/CMakeLists.txt b/Source/Android/jni/AndroidCommon/CMakeLists.txt new file mode 100644 index 0000000000..e8bcc22863 --- /dev/null +++ b/Source/Android/jni/AndroidCommon/CMakeLists.txt @@ -0,0 +1,15 @@ +add_library(androidcommon STATIC + AndroidCommon.cpp + AndroidCommon.h + IDCache.cpp + IDCache.h +) + +target_link_libraries(androidcommon +PRIVATE + android + log + "-Wl,--no-warn-mismatch" + "-Wl,--whole-archive" + "-Wl,--no-whole-archive" +) diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 0e2e9026c6..f44b5402f8 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -39,6 +39,9 @@ static jmethodID s_ini_file_section_constructor; static jclass s_compress_cb_class; static jmethodID s_compress_cb_run; +static jclass s_content_handler_class; +static jmethodID s_content_handler_open_fd; + namespace IDCache { JNIEnv* GetEnvForThread() @@ -174,6 +177,16 @@ jmethodID GetCompressCallbackRun() return s_compress_cb_run; } +jclass GetContentHandlerClass() +{ + return s_content_handler_class; +} + +jmethodID GetContentHandlerOpenFd() +{ + return s_content_handler_open_fd; +} + } // namespace IDCache #ifdef __cplusplus @@ -241,6 +254,12 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) s_compress_cb_class = reinterpret_cast(env->NewGlobalRef(compress_cb_class)); s_compress_cb_run = env->GetMethodID(s_compress_cb_class, "run", "(Ljava/lang/String;F)Z"); + const jclass content_handler_class = + env->FindClass("org/dolphinemu/dolphinemu/utils/ContentHandler"); + s_content_handler_class = reinterpret_cast(env->NewGlobalRef(content_handler_class)); + s_content_handler_open_fd = env->GetStaticMethodID(s_content_handler_class, "openFd", + "(Ljava/lang/String;Ljava/lang/String;)I"); + return JNI_VERSION; } @@ -258,6 +277,7 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) env->DeleteGlobalRef(s_ini_file_class); env->DeleteGlobalRef(s_ini_file_section_class); env->DeleteGlobalRef(s_compress_cb_class); + env->DeleteGlobalRef(s_content_handler_class); } #ifdef __cplusplus diff --git a/Source/Android/jni/AndroidCommon/IDCache.h b/Source/Android/jni/AndroidCommon/IDCache.h index b3dbd71554..54dafb5a0d 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.h +++ b/Source/Android/jni/AndroidCommon/IDCache.h @@ -41,4 +41,7 @@ jmethodID GetIniFileSectionConstructor(); jclass GetCompressCallbackClass(); jmethodID GetCompressCallbackRun(); +jclass GetContentHandlerClass(); +jmethodID GetContentHandlerOpenFd(); + } // namespace IDCache diff --git a/Source/Android/jni/CMakeLists.txt b/Source/Android/jni/CMakeLists.txt index e11e8dde76..2b8329f99a 100644 --- a/Source/Android/jni/CMakeLists.txt +++ b/Source/Android/jni/CMakeLists.txt @@ -1,7 +1,6 @@ add_library(main SHARED - AndroidCommon/AndroidCommon.cpp - AndroidCommon/IDCache.cpp GameList/GameFile.cpp + GameList/GameFile.h GameList/GameFileCache.cpp IniFile.cpp MainAndroid.cpp @@ -10,6 +9,7 @@ add_library(main SHARED target_link_libraries(main PRIVATE + androidcommon core uicommon ) @@ -32,3 +32,5 @@ file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/R file(REMOVE_RECURSE ${CMAKE_SOURCE_DIR}/Source/Android/app/src/main/assets/Sys/Themes/) set(CPACK_PACKAGE_EXECUTABLES ${CPACK_PACKAGE_EXECUTABLES} main) + +add_subdirectory(AndroidCommon) diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 6b65fc3a7d..015b03f882 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -64,8 +64,8 @@ if (MSVC) add_definitions(/I${PCH_DIRECTORY}) add_definitions(/Yu${PCH_PATH}) - # Don't include timestamps in binaries - add_link_options(/Brepro) + # Don't include timestamps in binaries + add_link_options(/Brepro) endif() # These aren't actually needed for C11/C++11 diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index a805635af6..9592043954 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -164,6 +164,11 @@ elseif(WIN32) if (_M_X86_64) target_link_libraries(common PRIVATE opengl32.lib) endif() +elseif (ANDROID) + target_link_libraries(common + PRIVATE + androidcommon + ) endif() if(ANDROID) diff --git a/Source/Core/Common/File.cpp b/Source/Core/Common/File.cpp index 9dfcdbd7d8..a724cbb723 100644 --- a/Source/Core/Common/File.cpp +++ b/Source/Core/Common/File.cpp @@ -15,6 +15,13 @@ #include #endif +#ifdef ANDROID +#include + +#include "Common/StringUtil.h" +#include "jni/AndroidCommon/AndroidCommon.h" +#endif + #include "Common/CommonTypes.h" #include "Common/File.h" #include "Common/FileUtil.h" @@ -62,7 +69,21 @@ bool IOFile::Open(const std::string& filename, const char openmode[]) #ifdef _WIN32 m_good = _tfopen_s(&m_file, UTF8ToTStr(filename).c_str(), UTF8ToTStr(openmode).c_str()) == 0; #else - m_file = std::fopen(filename.c_str(), openmode); +#ifdef ANDROID + if (StringBeginsWith(filename, "content://")) + { + // The Java method which OpenAndroidContent passes the mode to does not support the b specifier. + // Since we're on POSIX, it's fine to just remove the b. + std::string mode_without_b(openmode); + mode_without_b.erase(std::remove(mode_without_b.begin(), mode_without_b.end(), 'b'), + mode_without_b.end()); + m_file = fdopen(OpenAndroidContent(filename, mode_without_b), mode_without_b.c_str()); + } + else +#endif + { + m_file = std::fopen(filename.c_str(), openmode); + } m_good = m_file != nullptr; #endif