Add java-side prefix filtering for ContentUri directory listing

This commit is contained in:
Henrik Rydgård
2026-01-09 10:12:15 +01:00
parent 4e5e34a0cc
commit e6e82c9ad3
4 changed files with 26 additions and 8 deletions
+3 -2
View File
@@ -39,7 +39,7 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
openContentUri = env->GetStaticMethodID(localClass, "openContentUri", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
_dbg_assert_(openContentUri);
listContentUriDir = env->GetStaticMethodID(localClass, "listContentUriDir", "(Landroid/app/Activity;Ljava/lang/String;)[Ljava/lang/String;");
listContentUriDir = env->GetStaticMethodID(localClass, "listContentUriDir", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;");
_dbg_assert_(listContentUriDir);
contentUriCreateDirectory = env->GetStaticMethodID(localClass, "contentUriCreateDirectory", "(Landroid/app/Activity;Ljava/lang/String;Ljava/lang/String;)I");
_dbg_assert_(contentUriCreateDirectory);
@@ -265,7 +265,8 @@ std::vector<File::FileInfo> Android_ListContentUri(const std::string &uri, const
double start = time_now_d();
jstring param = env->NewStringUTF(uri.c_str());
jobject retval = env->CallStaticObjectMethod(g_classContentUri, listContentUriDir, g_nativeActivity, param);
jstring filenamePrefix = prefix.empty() ? nullptr : env->NewStringUTF(prefix.c_str());
jobject retval = env->CallStaticObjectMethod(g_classContentUri, listContentUriDir, g_nativeActivity, param, filenamePrefix);
jobjectArray fileList = (jobjectArray)retval;
std::vector<File::FileInfo> items;
+3
View File
@@ -26,6 +26,7 @@
#include "Common/System/System.h"
#include "Common/File/FileUtil.h"
#include "Common/File/DirListing.h"
#include "Common/Serialize/Serializer.h"
#include "Common/Serialize/SerializeFuncs.h"
#include "Common/StringUtils.h"
@@ -68,7 +69,9 @@ static const char * const LOAD_UNDO_NAME = "load_undo.ppst";
namespace SaveState {
// Used for "confirm exit if you haven't saved in a while"
double g_lastSaveTime = -1.0;
static bool needsProcess = false;
static bool needsRestart = false;
static std::mutex mutex;
+3 -1
View File
@@ -41,6 +41,8 @@ namespace SaveState {
// Cycle through the 5 savestate slots
void PrevSlot();
void NextSlot();
// Run the various actions directly.
void SaveSlot(const Path &gameFilename, int slot, Callback callback);
void LoadSlot(const Path &gameFilename, int slot, Callback callback);
bool UndoSaveSlot(const Path &gameFilename, int slot);
@@ -55,6 +57,7 @@ namespace SaveState {
bool HasUndoLoad(const Path &gameFilename);
bool HasScreenshotInSlot(const Path &gameFilename, int slot);
// Just returns the current slot from config.
int GetCurrentSlot();
// Returns -1 if there's no oldest/newest slot.
@@ -62,7 +65,6 @@ namespace SaveState {
int GetOldestSlot(const Path &gameFilename);
std::string GetSlotDateAsString(const Path &gameFilename, int slot);
std::string GenerateFullDiscId(const Path &gameFilename);
Path GenerateSaveSlotFilename(const Path &gameFilename, int slot, const char *extension);
std::string GetTitle(const Path &filename);
+17 -5
View File
@@ -19,6 +19,7 @@ import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.util.ArrayList;
import java.util.Locale;
import java.util.UUID;
public class ContentUri {
@@ -65,7 +66,7 @@ public class ContentUri {
DocumentsContract.Document.COLUMN_LAST_MODIFIED
};
private static String cursorToString(Cursor c) {
private static String cursorToString(Cursor c, String prefixLowercase) {
final int flags = c.getInt(2);
// Filter out any virtual or partial nonsense.
// There's a bunch of potentially-interesting flags here btw,
@@ -74,9 +75,16 @@ public class ContentUri {
if ((flags & (DocumentsContract.Document.FLAG_PARTIAL | DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT)) != 0) {
return null;
}
final String documentName = c.getString(0);
if (prefixLowercase != null && !prefixLowercase.isEmpty()) {
// Filter by prefix, case insensitive.
if (!documentName.toLowerCase(Locale.ROOT).startsWith(prefixLowercase)) {
return null;
}
}
final String mimeType = c.getString(3);
final boolean isDirectory = mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR);
final String documentName = c.getString(0);
final long size = isDirectory ? 0 : c.getLong(1);
final long lastModified = c.getLong(4);
@@ -160,7 +168,7 @@ public class ContentUri {
// uestions/42186820/documentfile-is-very-slow
@Keep
@SuppressWarnings("unused")
public static String[] listContentUriDir(Activity activity, String uriString) {
public static String[] listContentUriDir(Activity activity, String uriString, String prefix) {
try {
Uri uri = Uri.parse(uriString);
final ContentResolver resolver = activity.getContentResolver();
@@ -176,12 +184,16 @@ public class ContentUri {
DocumentsContract.Document.COLUMN_LAST_MODIFIED // index 4
};
if (prefix != null) {
prefix = prefix.toLowerCase(Locale.ROOT);
}
try (Cursor c = resolver.query(childrenUri, projection, null, null, null)) {
if (c == null) {
return new String[]{"X"};
}
while (c.moveToNext()) {
String str = cursorToString(c);
String str = cursorToString(c, prefix);
if (str != null) {
listing.add(str);
}
@@ -354,7 +366,7 @@ public class ContentUri {
try (Cursor c = activity.getContentResolver().query(Uri.parse(fileName), projection, null, null, null)) {
if (c != null && c.moveToFirst()) {
return cursorToString(c);
return cursorToString(c, null);
} else {
return null;
}