mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-23 13:30:02 +00:00
Start experimenting with DocumentsContract (the thing DocumentFile wraps)
wip Some progress towards making the file browser work with DOCUMENT_TREE More directory browsing progress More Scoped Storage hackery. Can now browse to a folder and use PPSSPP's game browser to load ISOs from it. Remove the defunct fdopendir approach. Buildfixes.
This commit is contained in:
parent
5030f1f719
commit
87a25fd230
@ -20,6 +20,7 @@
|
||||
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Net/URL.h"
|
||||
#include "Common/File/DirListing.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstdio>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
@ -30,10 +30,10 @@ struct FileInfo {
|
||||
bool GetFileInfo(const Path &path, FileInfo *fileInfo);
|
||||
|
||||
enum {
|
||||
GETFILES_GETHIDDEN = 1
|
||||
GETFILES_GETHIDDEN = 1,
|
||||
GETFILES_URIENCODE_ANDROID = 2, // Android shenanigans
|
||||
};
|
||||
|
||||
|
||||
size_t GetFilesInDir(const Path &directory, std::vector<FileInfo> *files, const char *filter = nullptr, int flags = 0);
|
||||
int64_t GetDirectoryRecursiveSize(const Path &path, const char *filter = nullptr, int flags = 0);
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
|
||||
#include "Common/File/PathBrowser.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/DirListing.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/Log.h"
|
||||
@ -16,6 +17,10 @@
|
||||
|
||||
#include "Core/System.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include "android/jni/app-android.h"
|
||||
#endif
|
||||
|
||||
bool LoadRemoteFileList(const Path &url, bool *cancel, std::vector<File::FileInfo> &files) {
|
||||
_dbg_assert_(url.Type() == PathType::HTTP);
|
||||
|
||||
@ -231,11 +236,11 @@ bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *
|
||||
while (!IsListingReady() && (!cancel || !*cancel)) {
|
||||
// In case cancel changes, just sleep.
|
||||
guard.unlock();
|
||||
sleep_ms(100);
|
||||
sleep_ms(50);
|
||||
guard.lock();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
#if PPSSPP_PLATFORM(WINDOWS)
|
||||
if (path_.IsRoot()) {
|
||||
// Special path that means root of file system.
|
||||
std::vector<std::string> drives = File::GetWindowsDrives();
|
||||
@ -254,6 +259,30 @@ bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (Android_IsContentUri(path_.ToString())) {
|
||||
std::vector<std::string> files = Android_ListContentUri(path_.ToString());
|
||||
fileInfo.clear();
|
||||
for (auto &file : files) {
|
||||
ERROR_LOG(FILESYS, "!! %s", file.c_str());
|
||||
std::vector<std::string> parts;
|
||||
SplitString(file, '|', parts);
|
||||
if (parts.size() != 4) {
|
||||
continue;
|
||||
}
|
||||
File::FileInfo info;
|
||||
info.exists = true;
|
||||
info.isDirectory = parts[0][0] == 'D';
|
||||
sscanf(parts[1].c_str(), "%ld", &info.size);
|
||||
info.name = parts[2];
|
||||
info.fullName = Path(parts[3]);
|
||||
info.isWritable = false; // We don't yet request write access
|
||||
fileInfo.push_back(info);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (path_.Type() == PathType::HTTP) {
|
||||
fileInfo = ApplyFilter(pendingFiles_, filter);
|
||||
return true;
|
||||
@ -272,6 +301,15 @@ bool PathBrowser::CanNavigateUp() {
|
||||
}
|
||||
#endif
|
||||
*/
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (Android_IsContentUri(path_.ToString())) {
|
||||
// Need to figure out how much we can navigate by parsing the URL.
|
||||
// DocumentUri from seems to be split into two paths: The folder you have gotten permission to see,
|
||||
// and the folder below it.
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
return path_.CanNavigateUp();
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ std::string UriDecode(const std::string & sSrc)
|
||||
return sResult;
|
||||
}
|
||||
|
||||
// Only alphanum is safe.
|
||||
// Only alphanum and underscore is safe.
|
||||
const char SAFE[256] =
|
||||
{
|
||||
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
|
||||
@ -165,7 +165,7 @@ const char SAFE[256] =
|
||||
/* 3 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
|
||||
|
||||
/* 4 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
|
||||
/* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
|
||||
/* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,1, // last here is underscore. it's ok.
|
||||
/* 6 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
|
||||
/* 7 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
|
||||
|
||||
|
@ -337,7 +337,7 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) {
|
||||
case IdentifiedFileType::UNKNOWN_ELF:
|
||||
case IdentifiedFileType::UNKNOWN:
|
||||
default:
|
||||
ERROR_LOG(LOADER, "Failed to identify file");
|
||||
ERROR_LOG(LOADER, "Failed to identify file: %s", fileLoader->GetPath().c_str());
|
||||
*error_string = "Failed to identify file";
|
||||
break;
|
||||
}
|
||||
|
@ -1206,34 +1206,7 @@ void MainScreen::sendMessage(const char *message, const char *value) {
|
||||
LaunchFile(screenManager(), Path(std::string(value)));
|
||||
}
|
||||
if (!strcmp(message, "browse_folderSelect")) {
|
||||
std::string filename;
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
// Hacky way to get a normal path from a Android Storage Framework path.
|
||||
// Is not gonna work forever, but ship-hack for 1.11.
|
||||
std::string url = value;
|
||||
const char *prefix = "content://com.android.externalstorage.documents/tree/";
|
||||
const char *primaryPrefix = "/storage/primary/";
|
||||
if (startsWith(url, prefix)) {
|
||||
url = UriDecode(url.substr(strlen(prefix)));
|
||||
size_t colonPos = url.find(":");
|
||||
if (colonPos != std::string::npos) {
|
||||
url[colonPos] = '/';
|
||||
}
|
||||
url = "/storage/" + url;
|
||||
if (startsWith(url, primaryPrefix)) {
|
||||
url = g_Config.memStickDirectory.ToString() + url.substr(strlen(primaryPrefix));
|
||||
}
|
||||
INFO_LOG(SYSTEM, "Translated '%s' into '%s'", value, url.c_str());
|
||||
} else {
|
||||
// It's not gonna work.
|
||||
// TODO: Show an error message?
|
||||
INFO_LOG(SYSTEM, "Failed to parse content string: '%s'", value);
|
||||
return;
|
||||
}
|
||||
filename = url;
|
||||
#else
|
||||
filename = value;
|
||||
#endif
|
||||
std::string filename = value;
|
||||
INFO_LOG(SYSTEM, "Got folder: '%s'", filename.c_str());
|
||||
int tab = tabHolder_->GetCurrentTab();
|
||||
// Don't allow browsing in the other tabs (I don't think it's possible to reach the option though)
|
||||
|
@ -482,13 +482,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
|
||||
host = new NativeHost();
|
||||
}
|
||||
#endif
|
||||
if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
|
||||
|
||||
g_Config.externalDirectory = Path(external_dir);
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE)) {
|
||||
g_Config.externalDirectory = Path(g_extFilesDir);
|
||||
#endif
|
||||
} else {
|
||||
g_Config.externalDirectory = Path(external_dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
g_Config.defaultCurrentDirectory = Path("/");
|
||||
g_Config.internalDataDirectory = Path(savegame_dir);
|
||||
|
@ -30,7 +30,7 @@
|
||||
<uses-permission-sdk-23 android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
<!-- AndroidX minimum SDK workaround. We don't care if it's broken on older versions. -->
|
||||
<uses-sdk tools:overrideLibrary="androidx.appcompat.resources,androidx.appcompat,androidx.fragment,androidx.drawerlayout,androidx.vectordrawable.animated,androidx.vectordrawable,androidx.viewpager,androidx.loader,androidx.activity,androidx.annotation,androidx.customview,androidx.cursoradapter,androidx.arch,androidx.collection,androidx.core,androidx.versionedparcelable,androidx.interpolator,androidx.lifecycle,androidx.loader,androidx.savedstate,androidx.lifecycle.viewmodel,androidx.lifecycle.livedata,androidx.lifecycle.livedata.core,androidx.arch.core"/>
|
||||
<uses-sdk tools:overrideLibrary="androidx.appcompat.resources,androidx.appcompat,androidx.fragment,androidx.drawerlayout,androidx.vectordrawable.animated,androidx.vectordrawable,androidx.viewpager,androidx.loader,androidx.activity,androidx.annotation,androidx.customview,androidx.cursoradapter,androidx.arch,androidx.collection,androidx.core,androidx.versionedparcelable,androidx.interpolator,androidx.lifecycle,androidx.loader,androidx.savedstate,androidx.lifecycle.viewmodel,androidx.lifecycle.livedata,androidx.lifecycle.livedata.core,androidx.arch.core,androidx.documentfile"/>
|
||||
|
||||
<supports-screens
|
||||
android:largeScreens="true"
|
||||
|
@ -15,7 +15,8 @@ dependencies {
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
// For loading and tinting drawables on older versions of the platform
|
||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
// implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
implementation "androidx.documentfile:documentfile:1.0.1"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -172,6 +172,7 @@ static float g_safeInsetBottom = 0.0;
|
||||
static jmethodID postCommand;
|
||||
|
||||
static jmethodID openContentUri;
|
||||
static jmethodID listContentUriDir;
|
||||
static jmethodID closeContentUri;
|
||||
|
||||
static jobject nativeActivity;
|
||||
@ -241,12 +242,42 @@ int Android_OpenContentUriFd(const std::string &filename) {
|
||||
if (!nativeActivity) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string fname = filename;
|
||||
// PPSSPP adds an ending slash to directories before looking them up.
|
||||
// TODO: Fix that in the caller (or don't call this for directories).
|
||||
if (fname.back() == '/')
|
||||
fname.pop_back();
|
||||
auto env = getEnv();
|
||||
jstring param = env->NewStringUTF(filename.c_str());
|
||||
jstring param = env->NewStringUTF(fname.c_str());
|
||||
int fd = env->CallIntMethod(nativeActivity, openContentUri, param);
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::vector<std::string> Android_ListContentUri(const std::string &path) {
|
||||
if (!nativeActivity) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
auto env = getEnv();
|
||||
jstring param = env->NewStringUTF(path.c_str());
|
||||
jobject retval = env->CallObjectMethod(nativeActivity, listContentUriDir, param);
|
||||
|
||||
jobjectArray fileList = (jobjectArray)retval;
|
||||
std::vector<std::string> items;
|
||||
int size = env->GetArrayLength(fileList);
|
||||
for (int i = 0; i < size; i++) {
|
||||
jstring str = (jstring) env->GetObjectArrayElement(fileList, i);
|
||||
const char *charArray = env->GetStringUTFChars(str, 0);
|
||||
if (charArray) { // paranoia
|
||||
items.push_back(std::string(charArray));
|
||||
}
|
||||
env->ReleaseStringUTFChars(str, charArray);
|
||||
env->DeleteLocalRef(str);
|
||||
}
|
||||
env->DeleteLocalRef(fileList);
|
||||
return items;
|
||||
}
|
||||
|
||||
class ContentURIFileLoader : public ProxiedFileLoader {
|
||||
public:
|
||||
ContentURIFileLoader(const Path &filename)
|
||||
@ -518,7 +549,11 @@ 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");
|
||||
_dbg_assert_(postCommand);
|
||||
openContentUri = env->GetMethodID(env->GetObjectClass(obj), "openContentUri", "(Ljava/lang/String;)I");
|
||||
_dbg_assert_(openContentUri);
|
||||
listContentUriDir = env->GetMethodID(env->GetObjectClass(obj), "listContentUriDir", "(Ljava/lang/String;)[Ljava/lang/String;");
|
||||
_dbg_assert_(listContentUriDir);
|
||||
}
|
||||
|
||||
extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) {
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "ppsspp_config.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/LogManager.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
@ -12,11 +14,17 @@
|
||||
jclass findClass(const char* name);
|
||||
JNIEnv* getEnv();
|
||||
|
||||
#endif
|
||||
|
||||
class AndroidLogger : public LogListener {
|
||||
public:
|
||||
void Log(const LogMessage &message) override;
|
||||
};
|
||||
|
||||
extern std::string g_extFilesDir;
|
||||
|
||||
// Called from PathBrowser for example.
|
||||
|
||||
bool Android_IsContentUri(const std::string &filename);
|
||||
int Android_OpenContentUriFd(const std::string &filename);
|
||||
std::vector<std::string> Android_ListContentUri(const std::string &filename);
|
||||
|
||||
#endif
|
||||
|
@ -27,6 +27,7 @@ import android.os.PowerManager;
|
||||
import android.os.Vibrator;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
@ -102,7 +103,7 @@ public abstract class NativeActivity extends Activity {
|
||||
private boolean shuttingDown;
|
||||
|
||||
private static final int RESULT_LOAD_IMAGE = 1;
|
||||
private static final int RESULT_BROWSE_FILE = 2;
|
||||
private static final int RESULT_OPEN_DOCUMENT = 2;
|
||||
private static final int RESULT_OPEN_DOCUMENT_TREE = 3;
|
||||
|
||||
// Allow for multiple connected gamepads but just consider them the same for now.
|
||||
@ -1141,7 +1142,7 @@ public abstract class NativeActivity extends Activity {
|
||||
cursor.close();
|
||||
NativeApp.sendMessage("bgImage_updated", picturePath);
|
||||
}
|
||||
} else if (requestCode == RESULT_BROWSE_FILE) {
|
||||
} else if (requestCode == RESULT_OPEN_DOCUMENT) {
|
||||
Uri selectedFile = data.getData();
|
||||
if (selectedFile != null) {
|
||||
// Grab permanent permission so we can show it in recents list etc.
|
||||
@ -1152,12 +1153,20 @@ public abstract class NativeActivity extends Activity {
|
||||
NativeApp.sendMessage("browse_fileSelect", selectedFile.toString());
|
||||
}
|
||||
} else if (requestCode == RESULT_OPEN_DOCUMENT_TREE) {
|
||||
Uri selectedFile = data.getData();
|
||||
if (selectedFile != null) {
|
||||
// Convert URI to normal path. (This might not be possible in Android 12+)
|
||||
String path = selectedFile.toString();
|
||||
Uri selectedDirectoryUri = data.getData();
|
||||
if (selectedDirectoryUri != null) {
|
||||
String path = selectedDirectoryUri.toString();
|
||||
Log.i(TAG, "Browse folder finished: " + path);
|
||||
NativeApp.sendMessage("browse_folderSelect", path);
|
||||
Log.i(TAG, "is tree:" + DocumentsContract.isTreeUri(selectedDirectoryUri));
|
||||
getContentResolver().takePersistableUriPermission(selectedDirectoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, selectedDirectoryUri);
|
||||
Log.i(TAG, "Document name: " + documentFile.getUri());
|
||||
DocumentFile[] children = documentFile.listFiles();
|
||||
for (DocumentFile child : children) {
|
||||
Log.i(TAG, "Child: " + child.getUri() + " " + child.getName());
|
||||
}
|
||||
NativeApp.sendMessage("browse_folderSelect", documentFile.getUri().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1296,8 +1305,11 @@ public abstract class NativeActivity extends Activity {
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("*/*");
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
//intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
|
||||
startActivityForResult(intent, RESULT_BROWSE_FILE);
|
||||
// Possible alternative approach:
|
||||
// String[] mimeTypes = {"application/octet-stream", "/x-iso9660-image"};
|
||||
// intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
||||
startActivityForResult(intent, RESULT_OPEN_DOCUMENT);
|
||||
// intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, e.toString());
|
||||
return false;
|
||||
@ -1307,7 +1319,7 @@ public abstract class NativeActivity extends Activity {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); // not yet used properly
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
|
||||
intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); // Only allow local folders.
|
||||
startActivityForResult(intent, RESULT_OPEN_DOCUMENT_TREE);
|
||||
return true;
|
||||
|
@ -8,6 +8,8 @@ import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.util.Log;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PpssppActivity extends NativeActivity {
|
||||
private static final String TAG = "PpssppActivity";
|
||||
@ -128,4 +130,31 @@ public class PpssppActivity extends NativeActivity {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public String[] listContentUriDir(String uriString) {
|
||||
try {
|
||||
Uri uri = Uri.parse(uriString);
|
||||
DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri);
|
||||
Log.i(TAG, "Listing content directory: " + documentFile.getUri());
|
||||
DocumentFile[] children = documentFile.listFiles();
|
||||
ArrayList<String> listing = new ArrayList<String>();
|
||||
// Encode entries into strings for JNI simplicity.
|
||||
for (DocumentFile file : children) {
|
||||
String typeStr = "F|";
|
||||
if (file.isDirectory()) {
|
||||
typeStr = "D|";
|
||||
}
|
||||
// TODO: Should we do something with child.isVirtual()?.
|
||||
typeStr += file.length() + "|" + file.getName() + "|" + file.getUri();
|
||||
Log.i(TAG, "> " + typeStr);
|
||||
listing.add(typeStr);
|
||||
}
|
||||
// Is ArrayList weird or what?
|
||||
String[] strings = new String[listing.size()];
|
||||
return listing.toArray(strings);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception opening content uri: " + e.toString());
|
||||
return new String[]{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user