Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use file ops for file:// and document ops for content:// #1495

Merged
merged 3 commits into from Nov 3, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
241 changes: 167 additions & 74 deletions internal/driver/gomobile/android.c
@@ -1,10 +1,12 @@
// +build android

#include <android/log.h>
#include <dirent.h>
#include <jni.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Fyne", __VA_ARGS__)

Expand Down Expand Up @@ -121,6 +123,7 @@ void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
jthrowable loadErr = (*env)->ExceptionOccurred(env);

if (loadErr != NULL) {
(*env)->ExceptionDescribe(loadErr);
(*env)->ExceptionClear(env);
return NULL;
}
Expand Down Expand Up @@ -155,93 +158,183 @@ void closeStream(uintptr_t jni_env, uintptr_t ctx, void* stream) {
(*env)->DeleteGlobalRef(env, stream);
}

bool uriCanList(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);

jclass contractClass = find_class(env, "android/provider/DocumentsContract");
if (contractClass == NULL) { // API 19
bool hasPrefix(char* string, char* prefix) {
size_t lp = strlen(prefix);
size_t ls = strlen(string);
if (ls < lp) {
return false;
}
jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
if (getDoc == NULL) { // API 21
return false;
}
jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);
return memcmp(prefix, string, lp) == 0;
}

jmethodID getTree = find_static_method(env, contractClass, "buildDocumentUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
jobject treeUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getTree, uri, docID);
bool uriCanList(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
if (hasPrefix(uriCstr, "file://")) {
// Get file path from URI
size_t length = strlen(uriCstr)-7;// -7 for 'file://'
char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
memcpy(path, &uriCstr[7], length);
path[length] = '\0';

jclass resolverClass = (*env)->GetObjectClass(env, resolver);
jmethodID getType = find_method(env, resolverClass, "getType", "(Landroid/net/Uri;)Ljava/lang/String;");
jstring type = (jstring)(*env)->CallObjectMethod(env, resolver, getType, treeUri);
// Stat path to determine if it points to a directory
struct stat statbuf;
int result = stat(path, &statbuf);

if (type == NULL) {
return false;
}
free(path);

if (result == 0) {
return S_ISDIR(statbuf.st_mode);
}
} else if (hasPrefix(uriCstr, "content://")) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);
jthrowable loadErr = (*env)->ExceptionOccurred(env);

if (loadErr != NULL) {
(*env)->ExceptionDescribe(loadErr);
(*env)->ExceptionClear(env);
return false;
}

jclass contractClass = find_class(env, "android/provider/DocumentsContract");
if (contractClass == NULL) { // API 19
return false;
}
jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
if (getDoc == NULL) { // API 21
return false;
}
jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);

jmethodID getTree = find_static_method(env, contractClass, "buildDocumentUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
jobject treeUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getTree, uri, docID);

jclass resolverClass = (*env)->GetObjectClass(env, resolver);
jmethodID getType = find_method(env, resolverClass, "getType", "(Landroid/net/Uri;)Ljava/lang/String;");
jstring type = (jstring)(*env)->CallObjectMethod(env, resolver, getType, treeUri);

char *str = getString(jni_env, ctx, type);
return strcmp(str, "vnd.android.document/directory") == 0;
if (type == NULL) {
return false;
}

char *str = getString(jni_env, ctx, type);
return strcmp(str, "vnd.android.document/directory") == 0;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should log here that the URI scheme was not known - there will be no other errors logged to help figure such a corner case out.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup good idea, should this be log_fatal, or just log_info?

return false;
}

char* uriList(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);
if (hasPrefix(uriCstr, "file://")) {

// Get file path from URI
stuartmscott marked this conversation as resolved.
Show resolved Hide resolved
size_t length = strlen(uriCstr)-7;// -7 for 'file://'
char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
memcpy(path, &uriCstr[7], length);
path[length] = '\0';

char *ret = NULL;
DIR *dfd;
if ((dfd = opendir(path)) != NULL) {
struct dirent *dp;
int len = 0;
while ((dp = readdir(dfd)) != NULL) {
if (strcmp(dp->d_name, ".") == 0) {
// Ignore current directory
continue;
}
if (strcmp(dp->d_name, "..") == 0) {
// Ignore parent directory
continue;
}
// append
char *old = ret;
len = len + strlen(uriCstr) + 1 + strlen(dp->d_name) + 1;
ret = malloc(sizeof(char)*(len+1));
if (old != NULL) {
strcpy(ret, old);
free(old);
}
strcat(ret, uriCstr);
strcat(ret, "/");
strcat(ret, dp->d_name);
strcat(ret, "|");
}
if (ret != NULL) {
ret[len-1] = '\0';
}
}

jclass contractClass = find_class(env, "android/provider/DocumentsContract");
if (contractClass == NULL) { // API 19
return "";
}
jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
if (getDoc == NULL) { // API 21
return "";
}
jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);
free(path);

jmethodID getChild = find_static_method(env, contractClass, "buildChildDocumentsUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
jobject childrenUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChild, uri, docID);
return ret;
} else if (hasPrefix(uriCstr, "content://")) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);
jthrowable loadErr = (*env)->ExceptionOccurred(env);

jclass stringClass = find_class(env, "java/lang/String");
jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "document_id"));
if (loadErr != NULL) {
(*env)->ExceptionDescribe(loadErr);
(*env)->ExceptionClear(env);
return "";
}

jclass resolverClass = (*env)->GetObjectClass(env, resolver);
jmethodID query = find_method(env, resolverClass, "query", "(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;");
if (getDoc == NULL) { // API 26
return "";
}
jclass contractClass = find_class(env, "android/provider/DocumentsContract");
if (contractClass == NULL) { // API 19
return "";
}
jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
if (getDoc == NULL) { // API 21
return "";
}
jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);

jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, childrenUri, project, NULL, NULL);
jclass cursorClass = (*env)->GetObjectClass(env, cursor);
jmethodID next = find_method(env, cursorClass, "moveToNext", "()Z");
jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");

char *ret = NULL;
int len = 0;
while (((jboolean)(*env)->CallBooleanMethod(env, cursor, next)) == JNI_TRUE) {
jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
jobject childUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChild, uri, name);
jclass uriClass = (*env)->GetObjectClass(env, childUri);
jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;");
jobject s = (*env)->CallObjectMethod(env, childUri, toString);

char *uid = getString(jni_env, ctx, name);

// append
char *old = ret;
len = len + strlen(uid) + 1;
ret = malloc(sizeof(char)*(len+1));
if (old != NULL) {
strcpy(ret, old);
free(old);
jmethodID getChild = find_static_method(env, contractClass, "buildChildDocumentsUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
jobject childrenUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChild, uri, docID);

jclass stringClass = find_class(env, "java/lang/String");
jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "document_id"));

jclass resolverClass = (*env)->GetObjectClass(env, resolver);
jmethodID query = find_method(env, resolverClass, "query", "(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;");
if (getDoc == NULL) { // API 26
return "";
}
strcat(ret, uid);
strcat(ret, "|");
}

ret[len-1] = '\0';
return ret;
}
jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, childrenUri, project, NULL, NULL);
jclass cursorClass = (*env)->GetObjectClass(env, cursor);
jmethodID next = find_method(env, cursorClass, "moveToNext", "()Z");
jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");

char *ret = NULL;
int len = 0;
while (((jboolean)(*env)->CallBooleanMethod(env, cursor, next)) == JNI_TRUE) {
jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
jobject childUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChild, uri, name);
jclass uriClass = (*env)->GetObjectClass(env, childUri);
jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;");
jobject s = (*env)->CallObjectMethod(env, childUri, toString);

char *uid = getString(jni_env, ctx, name);

// append
char *old = ret;
len = len + strlen(uid) + 1;
ret = malloc(sizeof(char)*(len+1));
if (old != NULL) {
strcpy(ret, old);
free(old);
}
strcat(ret, uid);
strcat(ret, "|");
}

if (ret != NULL) {
ret[len-1] = '\0';
}
return ret;
}
return "";
}