mirror of
https://github.com/hrydgard/ppsspp.git
synced 2024-11-26 23:10:38 +00:00
Implement file filters for Android in PathBrowser
asdf Move Android file listing parsing logic into app-android.cpp. Add utility for parsing/writing Android Content Storage URIs. Fix some bugs. Allow upwards navigation in file browser from directories downstream from tree URIs.
This commit is contained in:
parent
87a25fd230
commit
e3cf04bb34
@ -19,6 +19,7 @@
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
#endif
|
||||
|
||||
bool LoadRemoteFileList(const Path &url, bool *cancel, std::vector<File::FileInfo> &files) {
|
||||
@ -261,22 +262,25 @@ bool PathBrowser::GetListing(std::vector<File::FileInfo> &fileInfo, const char *
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (Android_IsContentUri(path_.ToString())) {
|
||||
std::vector<std::string> files = Android_ListContentUri(path_.ToString());
|
||||
std::vector<File::FileInfo> 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;
|
||||
|
||||
std::vector<std::string> allowedExtensions;
|
||||
SplitString(filter, ':', allowedExtensions);
|
||||
|
||||
for (auto &info : files) {
|
||||
if (!info.isDirectory && allowedExtensions.size()) {
|
||||
bool found = false;
|
||||
for (auto &ext : allowedExtensions) {
|
||||
if (endsWithNoCase(info.name, "." + ext)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
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;
|
||||
@ -303,10 +307,8 @@ bool PathBrowser::CanNavigateUp() {
|
||||
*/
|
||||
#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;
|
||||
AndroidStorageContentURI uri(path_.ToString());
|
||||
return uri.CanNavigateUp();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -314,6 +316,16 @@ bool PathBrowser::CanNavigateUp() {
|
||||
}
|
||||
|
||||
void PathBrowser::NavigateUp() {
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
if (Android_IsContentUri(path_.ToString())) {
|
||||
// Manipulate the Uri to navigate upwards.
|
||||
AndroidStorageContentURI uri(path_.ToString());
|
||||
if (uri.NavigateUp()) {
|
||||
path_ = Path(uri.ToString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
path_ = path_.NavigateUp();
|
||||
}
|
||||
|
||||
|
@ -1006,6 +1006,7 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</ExcludedFromBuild>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\android\jni\AndroidContentURI.h" />
|
||||
<ClInclude Include="..\android\jni\AndroidVulkanContext.h">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
|
@ -538,6 +538,9 @@
|
||||
<ClInclude Include="..\android\jni\OpenSLContext.h">
|
||||
<Filter>Other Platforms\Android</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\android\jni\AndroidContentURI.h">
|
||||
<Filter>Other Platforms\Android</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="icon1.ico">
|
||||
|
107
android/jni/AndroidContentURI.h
Normal file
107
android/jni/AndroidContentURI.h
Normal file
@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Net/URL.h"
|
||||
|
||||
// Utility to deal with Android storage URIs of the forms:
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
|
||||
|
||||
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
|
||||
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
|
||||
// consider them pretty stable.
|
||||
class AndroidStorageContentURI {
|
||||
private:
|
||||
std::string provider;
|
||||
std::string root;
|
||||
std::string file;
|
||||
public:
|
||||
AndroidStorageContentURI() {}
|
||||
explicit AndroidStorageContentURI(const std::string &path) {
|
||||
Parse(path);
|
||||
}
|
||||
|
||||
bool Parse(const std::string &path) {
|
||||
const char *prefix = "content://";
|
||||
if (!startsWith(path, prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string components = path.substr(strlen(prefix));
|
||||
|
||||
std::vector<std::string> parts;
|
||||
SplitString(components, '/', parts);
|
||||
if (parts.size() == 3) {
|
||||
provider = parts[0];
|
||||
if (parts[1] != "tree") {
|
||||
return false;
|
||||
}
|
||||
root = UriDecode(parts[2]);
|
||||
return true;
|
||||
} else if (parts.size() == 5) {
|
||||
provider = parts[0];
|
||||
if (parts[1] != "tree") {
|
||||
return false;
|
||||
}
|
||||
root = UriDecode(parts[2]);
|
||||
if (parts[3] != "document") {
|
||||
return false;
|
||||
}
|
||||
file = UriDecode(parts[4]);
|
||||
// Sanity check file path.
|
||||
return startsWith(file, root);
|
||||
} else {
|
||||
// Invalid Content URI
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsTreeURI() const {
|
||||
return file.empty();
|
||||
}
|
||||
|
||||
bool CanNavigateUp() const {
|
||||
return file.size() > root.size();
|
||||
}
|
||||
|
||||
bool NavigateUp() {
|
||||
if (!CanNavigateUp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t slash = file.rfind('/');
|
||||
if (slash == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = file.substr(0, slash);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TreeContains(const AndroidStorageContentURI &fileURI) {
|
||||
if (!IsTreeURI()) {
|
||||
return false;
|
||||
}
|
||||
return startsWith(fileURI.file, root);
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
if (file.empty()) {
|
||||
// Tree URI
|
||||
return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str());
|
||||
} else {
|
||||
// File URI
|
||||
return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &FilePath() const {
|
||||
return file;
|
||||
}
|
||||
|
||||
const std::string &RootPath() const {
|
||||
return root;
|
||||
}
|
||||
};
|
@ -254,22 +254,43 @@ int Android_OpenContentUriFd(const std::string &filename) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
std::vector<std::string> Android_ListContentUri(const std::string &path) {
|
||||
// Empty string means no parent
|
||||
std::string Android_GetContentUriParent(const std::string &uri) {
|
||||
// Might attempt to implement this with path manipulation later, but that's
|
||||
// not reliable.
|
||||
return "";
|
||||
}
|
||||
|
||||
std::vector<File::FileInfo> Android_ListContentUri(const std::string &path) {
|
||||
if (!nativeActivity) {
|
||||
return std::vector<std::string>();
|
||||
return std::vector<File::FileInfo>();
|
||||
}
|
||||
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;
|
||||
std::vector<File::FileInfo> 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));
|
||||
std::string file = charArray;
|
||||
INFO_LOG(FILESYS, "!! %s", file.c_str());
|
||||
std::vector<std::string> parts;
|
||||
SplitString(file, '|', parts);
|
||||
if (parts.size() != 4) {
|
||||
continue;
|
||||
}
|
||||
File::FileInfo info;
|
||||
info.name = parts[2];
|
||||
info.isDirectory = parts[0][0] == 'D';
|
||||
info.exists = true;
|
||||
sscanf(parts[1].c_str(), "%ld", &info.size);
|
||||
info.fullName = Path(parts[3]);
|
||||
info.isWritable = false; // We don't yet request write access
|
||||
items.push_back(info);
|
||||
}
|
||||
env->ReleaseStringUTFChars(str, charArray);
|
||||
env->DeleteLocalRef(str);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "Common/LogManager.h"
|
||||
#include "Common/File/DirListing.h"
|
||||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
|
||||
@ -23,8 +24,11 @@ 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);
|
||||
bool Android_IsContentUri(const std::string &uri);
|
||||
int Android_OpenContentUriFd(const std::string &uri);
|
||||
std::string Android_GetContentUriParent(const std::string &uri); // Empty string means no parent
|
||||
|
||||
|
||||
std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri);
|
||||
|
||||
#endif
|
||||
|
@ -135,7 +135,6 @@ public class PpssppActivity extends NativeActivity {
|
||||
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.
|
||||
@ -146,7 +145,6 @@ public class PpssppActivity extends NativeActivity {
|
||||
}
|
||||
// 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?
|
||||
|
@ -58,6 +58,8 @@
|
||||
#include "Core/MIPS/MIPSVFPUUtils.h"
|
||||
#include "GPU/Common/TextureDecoder.h"
|
||||
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
#include "unittest/JitHarness.h"
|
||||
#include "unittest/TestVertexJit.h"
|
||||
#include "unittest/UnitTest.h"
|
||||
@ -606,6 +608,28 @@ static bool TestPath() {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TestAndroidContentURI() {
|
||||
static const char *treeURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO";
|
||||
static const char *directoryURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO";
|
||||
static const char *fileURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO%2FTekken%206.iso";
|
||||
|
||||
AndroidStorageContentURI treeURI;
|
||||
EXPECT_TRUE(treeURI.Parse(std::string(treeURIString)));
|
||||
AndroidStorageContentURI fileURI;
|
||||
EXPECT_TRUE(fileURI.Parse(std::string(fileURIString)));
|
||||
|
||||
EXPECT_TRUE(treeURI.TreeContains(fileURI));
|
||||
|
||||
EXPECT_TRUE(fileURI.CanNavigateUp());
|
||||
fileURI.NavigateUp();
|
||||
EXPECT_FALSE(fileURI.CanNavigateUp());
|
||||
|
||||
EXPECT_EQ_STR(fileURI.FilePath(), fileURI.RootPath());
|
||||
|
||||
EXPECT_EQ_STR(fileURI.ToString(), std::string(directoryURIString));
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef bool (*TestFunc)();
|
||||
struct TestItem {
|
||||
const char *name;
|
||||
@ -643,6 +667,7 @@ TestItem availableTests[] = {
|
||||
TEST_ITEM(MemMap),
|
||||
TEST_ITEM(ShaderGenerators),
|
||||
TEST_ITEM(Path),
|
||||
TEST_ITEM(AndroidContentURI),
|
||||
};
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
|
Loading…
Reference in New Issue
Block a user