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.
This commit is contained in:
Henrik Rydgård 2021-02-27 13:48:07 +01:00
parent 35ad3106ff
commit 54c9e28444
10 changed files with 100 additions and 13 deletions

View file

@ -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<std::string> cleanedRecent;
for (size_t i = 0; i < recentIsos.size(); i++) {

View file

@ -31,8 +31,28 @@
#include <fcntl.h>
#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()) {

View file

@ -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_;
};

View file

@ -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";

View file

@ -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));

View file

@ -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)

View file

@ -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<FileLoaderFactory> factory(new AndroidContentLoaderFactory());
// Register a content URI file loader.
RegisterFileLoaderFactory("content://", std::move(factory));
// No need to use EARLY_LOG anymore.
retry:

View file

@ -2,6 +2,7 @@
#include "ppsspp_config.h"
#include <string>
#include "Common/LogManager.h"
#if PPSSPP_PLATFORM(ANDROID)

View file

@ -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();

View file

@ -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;
}
}
}