Common/FileSystem: Make file functions content URI-aware

This commit is contained in:
Connor McLaughlin
2021-04-17 19:58:41 +10:00
parent 03f3f0369c
commit d6d8d21eff
5 changed files with 459 additions and 8 deletions

View File

@ -970,7 +970,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
jclass emulation_activity_class;
if ((s_AndroidHostInterface_constructor =
env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
env->GetMethodID(s_AndroidHostInterface_class, "<init>",
"(Landroid/content/Context;Lcom/github/stenzek/duckstation/FileHelper;)V")) == nullptr ||
(s_AndroidHostInterface_field_mNativePointer =
env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr ||
(s_AndroidHostInterface_method_reportError =
@ -1067,12 +1068,13 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setThreadAffinity, jobject unu
}
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object,
jstring user_directory)
jobject file_helper_object, jstring user_directory)
{
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
// initialize the java side
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object);
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object,
file_helper_object);
if (!java_obj)
{
Log_ErrorPrint("Failed to create Java AndroidHostInterface");
@ -1096,6 +1098,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, job
env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer,
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
FileSystem::SetAndroidFileHelper(s_jvm, env, file_helper_object);
return java_obj;
}
@ -1195,7 +1198,7 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisType, jobject
jstring axis_name)
{
std::optional<ControllerType> type =
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
if (!type)
return -1;
@ -1342,7 +1345,7 @@ static jobject CreateGameListEntry(JNIEnv* env, AndroidHostInterface* hi, const
{
const Timestamp modified_ts(
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
const std::string file_title_str(System::GetTitleForPath(entry.path.c_str()));
const std::string file_title_str(FileSystem::GetFileTitleFromPath(entry.path));
const std::string cover_path_str(hi->GetGameList()->GetCoverImagePathForEntry(&entry));
jstring path = env->NewStringUTF(entry.path.c_str());

View File

@ -23,9 +23,11 @@ public class AndroidHostInterface {
private long mNativePointer;
private Context mContext;
private FileHelper mFileHelper;
public AndroidHostInterface(Context context) {
public AndroidHostInterface(Context context, FileHelper fileHelper) {
this.mContext = context;
this.mFileHelper = fileHelper;
}
public void reportError(String message) {
@ -54,7 +56,7 @@ public class AndroidHostInterface {
static public native boolean setThreadAffinity(int[] cpus);
static public native AndroidHostInterface create(Context context, String userDirectory);
static public native AndroidHostInterface create(Context context, FileHelper fileHelper, String userDirectory);
public native boolean isEmulationThreadRunning();
@ -184,7 +186,7 @@ public class AndroidHostInterface {
mUserDirectory += "/duckstation";
Log.i("AndroidHostInterface", "User directory: " + mUserDirectory);
mInstance = create(context, mUserDirectory);
mInstance = create(context, new FileHelper(context), mUserDirectory);
return mInstance != null;
}

View File

@ -0,0 +1,159 @@
package com.github.stenzek.duckstation;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import java.util.ArrayList;
/**
* File helper class - used to bridge native code to Java storage access framework APIs.
*/
public class FileHelper {
/**
* Native filesystem flags.
*/
public static final int FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY = 1;
public static final int FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY = 2;
public static final int FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED = 4;
/**
* Native filesystem find result flags.
*/
public static final int FILESYSTEM_FIND_RECURSIVE = (1 << 0);
public static final int FILESYSTEM_FIND_RELATIVE_PATHS = (1 << 1);
public static final int FILESYSTEM_FIND_HIDDEN_FILES = (1 << 2);
public static final int FILESYSTEM_FIND_FOLDERS = (1 << 3);
public static final int FILESYSTEM_FIND_FILES = (1 << 4);
public static final int FILESYSTEM_FIND_KEEP_ARRAY = (1 << 5);
/**
* Projection used when searching for files.
*/
private static final String[] findProjection = new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_LAST_MODIFIED
};
private final Context context;
private final ContentResolver contentResolver;
/**
* File helper class - used to bridge native code to Java storage access framework APIs.
* @param context Context in which to perform file actions as.
*/
public FileHelper(Context context) {
this.context = context;
this.contentResolver = context.getContentResolver();
}
/**
* Retrieves a file descriptor for a content URI string. Called by native code.
* @param uriString string of the URI to open
* @param mode Java open mode
* @return file descriptor for URI, or -1
*/
public int openURIAsFileDescriptor(String uriString, String mode) {
try {
final Uri uri = Uri.parse(uriString);
final ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, mode);
if (fd == null)
return -1;
return fd.detachFd();
} catch (Exception e) {
return -1;
}
}
/**
* Recursively iterates documents in the specified tree, searching for files.
* @param treeUri Root tree in which to search for documents.
* @param documentId Document ID representing the directory to start searching.
* @param flags Native search flags.
* @param results Cumulative result array.
*/
private void doFindFiles(Uri treeUri, String documentId, int flags, ArrayList<FindResult> results) {
try {
final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId);
final Cursor cursor = contentResolver.query(queryUri, findProjection, null, null, null);
final int count = cursor.getCount();
while (cursor.moveToNext()) {
try {
final String mimeType = cursor.getString(2);
final String childDocumentId = cursor.getString(0);
final Uri uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocumentId);
final long size = cursor.getLong(3);
final long lastModified = cursor.getLong(4);
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
if ((flags & FILESYSTEM_FIND_FOLDERS) != 0) {
results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY));
}
if ((flags & FILESYSTEM_FIND_RECURSIVE) != 0)
doFindFiles(treeUri, childDocumentId, flags, results);
} else {
if ((flags & FILESYSTEM_FIND_FILES) != 0) {
results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, 0));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
cursor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Recursively iterates documents in the specified URI, searching for files.
* @param uriString URI containing directory to search.
* @param flags Native filter flags.
* @return Array of find results.
*/
public FindResult[] findFiles(String uriString, int flags) {
try {
final Uri fullUri = Uri.parse(uriString);
final String documentId = DocumentsContract.getTreeDocumentId(fullUri);
final ArrayList<FindResult> results = new ArrayList<>();
doFindFiles(fullUri, documentId, flags, results);
if (results.isEmpty())
return null;
final FindResult[] resultsArray = new FindResult[results.size()];
results.toArray(resultsArray);
return resultsArray;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* Java class containing the data for a file in a find operation.
*/
public static class FindResult {
public String name;
public String relativeName;
public long size;
public long modifiedTime;
public int flags;
public FindResult(String relativeName, String name, long size, long modifiedTime, int flags) {
this.relativeName = relativeName;
this.name = name;
this.size = size;
this.modifiedTime = modifiedTime;
this.flags = flags;
}
}
}