From 54c9e28444bb150cda0605bd45a3cb30c790ca37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 27 Feb 2021 13:48:07 +0100 Subject: [PATCH] Android: Implement opening ISOs through a file picker and Storage Access Framework Has issues with the recent list - fails to open during shutdown due to no activity, there's a little race to fix. --- Core/Config.cpp | 2 + Core/FileLoaders/LocalFileLoader.cpp | 36 ++++++++++----- Core/FileLoaders/LocalFileLoader.h | 3 ++ Core/Loaders.cpp | 5 ++- UI/DevScreens.cpp | 3 ++ UI/MainScreen.cpp | 4 ++ android/jni/app-android.cpp | 45 ++++++++++++++++++- android/jni/app-android.h | 1 + .../src/org/ppsspp/ppsspp/NativeActivity.java | 1 + .../src/org/ppsspp/ppsspp/PpssppActivity.java | 13 ++++++ 10 files changed, 100 insertions(+), 13 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index bf95896234..5a789f6d7e 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1529,6 +1529,8 @@ void Config::RemoveRecent(const std::string &file) { } } +// TODO: This can be called during shutdown at a point where we have no NativeActivity object. +// That causes us to drop all content URI isos since they fail to open. void Config::CleanRecent() { std::vector cleanedRecent; for (size_t i = 0; i < recentIsos.size(); i++) { diff --git a/Core/FileLoaders/LocalFileLoader.cpp b/Core/FileLoaders/LocalFileLoader.cpp index 3f05fb4618..9af65c1887 100644 --- a/Core/FileLoaders/LocalFileLoader.cpp +++ b/Core/FileLoaders/LocalFileLoader.cpp @@ -31,8 +31,28 @@ #include #endif +#ifndef _WIN32 +LocalFileLoader::LocalFileLoader(int fd, const std::string &filename) : fd_(fd), filename_(filename), isOpenedByFd_(fd != -1) { + if (fd != -1) { + DetectSizeFd(); + } +} + +void LocalFileLoader::DetectSizeFd() { +#if PPSSPP_PLATFORM(ANDROID) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) + off64_t off = lseek64(fd_, 0, SEEK_END); + filesize_ = off; + lseek64(fd_, 0, SEEK_SET); +#else + off_t off = lseek(fd_, 0, SEEK_END); + filesize_ = off; + lseek(fd_, 0, SEEK_SET); +#endif +} +#endif + LocalFileLoader::LocalFileLoader(const std::string &filename) - : filesize_(0), filename_(filename) { + : filesize_(0), filename_(filename), isOpenedByFd_(false) { if (filename.empty()) { ERROR_LOG(FILESYS, "LocalFileLoader can't load empty filenames"); return; @@ -43,15 +63,8 @@ LocalFileLoader::LocalFileLoader(const std::string &filename) if (fd_ == -1) { return; } -#if PPSSPP_PLATFORM(ANDROID) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) - off64_t off = lseek64(fd_, 0, SEEK_END); - filesize_ = off; - lseek64(fd_, 0, SEEK_SET); -#else - off_t off = lseek(fd_, 0, SEEK_END); - filesize_ = off; - lseek(fd_, 0, SEEK_SET); -#endif + + DetectSizeFd(); #else // _WIN32 @@ -92,6 +105,9 @@ LocalFileLoader::~LocalFileLoader() { bool LocalFileLoader::Exists() { // If we couldn't open it for reading, we say it does not exist. #ifndef _WIN32 + if (isOpenedByFd_) { + return true; + } if (fd_ != -1 || IsDirectory()) { #else if (handle_ != INVALID_HANDLE_VALUE || IsDirectory()) { diff --git a/Core/FileLoaders/LocalFileLoader.h b/Core/FileLoaders/LocalFileLoader.h index bb5910494f..d3933b55c4 100644 --- a/Core/FileLoaders/LocalFileLoader.h +++ b/Core/FileLoaders/LocalFileLoader.h @@ -27,6 +27,7 @@ typedef void *HANDLE; class LocalFileLoader : public FileLoader { public: LocalFileLoader(const std::string &filename); + LocalFileLoader(const int fd, const std::string &filename); virtual ~LocalFileLoader(); virtual bool Exists() override; @@ -37,6 +38,7 @@ public: private: #ifndef _WIN32 + void DetectSizeFd(); int fd_; #else HANDLE handle_; @@ -44,4 +46,5 @@ private: u64 filesize_; std::string filename_; std::mutex readLock_; + bool isOpenedByFd_; }; diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 2eebf30474..f61b499cdd 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -50,7 +50,7 @@ FileLoader *ConstructFileLoader(const std::string &filename) { } for (auto &iter : factories) { - if (startsWith(iter.first, filename)) { + if (startsWith(filename, iter.first)) { return iter.second->ConstructFileLoader(filename); } } @@ -126,6 +126,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader) { size_t readSize = fileLoader->ReadAt(0, 4, 1, &id); if (readSize != 1) { + ERROR_LOG(LOADER, "Failed to read identification bytes"); return IdentifiedFileType::ERROR_IDENTIFYING; } @@ -293,7 +294,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { break; case IdentifiedFileType::ERROR_IDENTIFYING: - ERROR_LOG(LOADER, "Could not read file"); + ERROR_LOG(LOADER, "Could not read file enough to identify it"); *error_string = fileLoader ? fileLoader->LatestError() : ""; if (error_string->empty()) *error_string = "Error reading file"; diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index bbbfb27848..633ac9c4f7 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -596,6 +596,9 @@ void SystemInfoScreen::CreateViews() { #if PPSSPP_PLATFORM(ANDROID) storage->Add(new InfoItem("ExtFilesDir", g_extFilesDir)); + if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) { + storage->Add(new InfoItem("Scoped Storage", di->T("Yes"))); + } #endif ViewGroup *buildConfigScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT)); diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index e8910f07ec..abaec2e11a 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -1183,6 +1183,10 @@ void MainScreen::sendMessage(const char *message, const char *value) { if (!strcmp(message, "boot")) { LaunchFile(screenManager(), std::string(value)); } + if (!strcmp(message, "browse_fileSelect")) { + INFO_LOG(SYSTEM, "Attempting to launch: '%s'", value); + LaunchFile(screenManager(), std::string(value)); + } if (!strcmp(message, "browse_folderSelect")) { std::string filename; #if PPSSPP_PLATFORM(ANDROID) diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 0690c702a2..664f69dc72 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -81,6 +81,7 @@ struct JNIEnv {}; #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/Loaders.h" +#include "Core/FileLoaders/LocalFileLoader.h" #include "Core/System.h" #include "Core/HLE/sceUsbCam.h" #include "Core/HLE/sceUsbGps.h" @@ -168,6 +169,10 @@ static float g_safeInsetTop = 0.0; static float g_safeInsetBottom = 0.0; static jmethodID postCommand; + +static jmethodID openContentUri; +static jmethodID closeContentUri; + static jobject nativeActivity; static volatile bool exitRenderLoop; static bool renderLoopRunning; @@ -227,6 +232,36 @@ void AndroidLogger::Log(const LogMessage &message) { } } +bool Android_IsContentUri(const std::string &filename) { + return startsWith(filename, "content://"); +} + +int Android_OpenContentUriFd(const std::string &filename) { + if (!nativeActivity) { + return -1; + } + auto env = getEnv(); + jstring param = env->NewStringUTF(filename.c_str()); + int fd = env->CallIntMethod(nativeActivity, openContentUri, param); + return fd; +} + +void Android_CloseContentUriFd(int fd) { + if (!fd) { + return; + } +} + +class AndroidContentLoaderFactory : public FileLoaderFactory { +public: + AndroidContentLoaderFactory() {} + FileLoader *ConstructFileLoader(const std::string &filename) override { + int fd = Android_OpenContentUriFd(filename); + INFO_LOG(SYSTEM, "Fd %d for content URI: '%s'", fd, filename.c_str()); + return new LocalFileLoader(fd, filename); + } +}; + JNIEnv* getEnv() { JNIEnv *env; int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6); @@ -438,7 +473,8 @@ bool System_GetPropertyBool(SystemProperty prop) { case SYSPROP_HAS_IMAGE_BROWSER: return true; case SYSPROP_HAS_FILE_BROWSER: - return false; // We kind of have but needs more work. + // return System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE); + return androidVersion >= 21; case SYSPROP_HAS_FOLDER_BROWSER: // Uses OPEN_DOCUMENT_TREE to let you select a folder. return androidVersion >= 21; @@ -470,6 +506,7 @@ std::string GetJavaString(JNIEnv *env, jstring jstr) { extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) { nativeActivity = env->NewGlobalRef(obj); postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V"); + openContentUri = env->GetMethodID(env->GetObjectClass(obj), "openContentUri", "(Ljava/lang/String;)I"); } extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) { @@ -651,6 +688,12 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str()); + + std::unique_ptr factory(new AndroidContentLoaderFactory()); + + // Register a content URI file loader. + RegisterFileLoaderFactory("content://", std::move(factory)); + // No need to use EARLY_LOG anymore. retry: diff --git a/android/jni/app-android.h b/android/jni/app-android.h index 276d55810f..3a4935d63b 100644 --- a/android/jni/app-android.h +++ b/android/jni/app-android.h @@ -2,6 +2,7 @@ #include "ppsspp_config.h" +#include #include "Common/LogManager.h" #if PPSSPP_PLATFORM(ANDROID) diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 8b3aae2eea..94e21ac74d 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1146,6 +1146,7 @@ public abstract class NativeActivity extends Activity { if (selectedFile != null) { // NativeApp.sendMessage("br"); Log.i(TAG, "Browse file finished:" + selectedFile.toString()); + NativeApp.sendMessage("browse_fileSelect", selectedFile.toString()); } } else if (requestCode == RESULT_OPEN_DOCUMENT_TREE) { Uri selectedFile = data.getData(); diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 4269f7f98b..24c3e77502 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -6,6 +6,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Looper; +import android.os.ParcelFileDescriptor; import android.util.Log; public class PpssppActivity extends NativeActivity { @@ -112,4 +113,16 @@ public class PpssppActivity extends NativeActivity { } }); } + + public int openContentUri(String uriString) { + try { + Uri uri = Uri.parse(uriString); + ParcelFileDescriptor filePfd = getContentResolver().openFileDescriptor(uri, "r"); + int fd = filePfd.detachFd(); // Take ownership of the fd. + return fd; + } catch (Exception e) { + Log.e(TAG, "Exception opening content uri: " + e.toString()); + return -1; + } + } }