| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- //go:build android
- // +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__)
- static jclass find_class(JNIEnv *env, const char *class_name) {
- jclass clazz = (*env)->FindClass(env, class_name);
- if (clazz == NULL) {
- (*env)->ExceptionClear(env);
- LOG_FATAL("cannot find %s", class_name);
- return NULL;
- }
- return clazz;
- }
- static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
- jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
- if (m == 0) {
- (*env)->ExceptionClear(env);
- LOG_FATAL("cannot find method %s %s", name, sig);
- return 0;
- }
- return m;
- }
- static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
- jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig);
- if (m == 0) {
- (*env)->ExceptionClear(env);
- LOG_FATAL("cannot find method %s %s", name, sig);
- return 0;
- }
- return m;
- }
- const char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) {
- JNIEnv *env = (JNIEnv*)jni_env;
- const char *chars = (*env)->GetStringUTFChars(env, str, NULL);
- const char *copy = strdup(chars);
- (*env)->ReleaseStringUTFChars(env, str, chars);
- return copy;
- }
- jobject parseURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
- jclass uriClass = find_class(env, "android/net/Uri");
- jmethodID parse = find_static_method(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
- return (jobject)(*env)->CallStaticObjectMethod(env, uriClass, parse, uriStr);
- }
- // clipboard
- jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
- jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
- jstring service = (*env)->NewStringUTF(env, "clipboard");
- jobject ret = (*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service);
- jthrowable err = (*env)->ExceptionOccurred(env);
- if (err != NULL) {
- LOG_FATAL("cannot lookup clipboard");
- (*env)->ExceptionClear(env);
- return NULL;
- }
- return ret;
- }
- const char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject mgr = getClipboard(jni_env, ctx);
- if (mgr == NULL) {
- return NULL;
- }
- jclass mgrClass = (*env)->GetObjectClass(env, mgr);
- jmethodID getText = find_method(env, mgrClass, "getText", "()Ljava/lang/CharSequence;");
- jobject content = (jstring)(*env)->CallObjectMethod(env, mgr, getText);
- if (content == NULL) {
- return NULL;
- }
- jclass clzCharSequence = (*env)->GetObjectClass(env, content);
- jmethodID toString = (*env)->GetMethodID(env, clzCharSequence, "toString", "()Ljava/lang/String;");
- jobject s = (*env)->CallObjectMethod(env, content, toString);
- return getString(jni_env, ctx, s);
- }
- void setClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *content) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject mgr = getClipboard(jni_env, ctx);
- if (mgr == NULL) {
- return;
- }
- jclass mgrClass = (*env)->GetObjectClass(env, mgr);
- jmethodID setText = find_method(env, mgrClass, "setText", "(Ljava/lang/CharSequence;)V");
- jstring str = (*env)->NewStringUTF(env, content);
- (*env)->CallVoidMethod(env, mgr, setText, str);
- }
- // file handling
- jobject getContentResolver(uintptr_t jni_env, uintptr_t ctx) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
- jmethodID getContentResolver = find_method(env, ctxClass, "getContentResolver", "()Landroid/content/ContentResolver;");
- return (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getContentResolver);
- }
- void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject resolver = getContentResolver(jni_env, ctx);
- jclass resolverClass = (*env)->GetObjectClass(env, resolver);
- jmethodID openInputStream = find_method(env, resolverClass, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;");
- jobject uri = parseURI(jni_env, ctx, uriCstr);
- jobject stream = (jobject)(*env)->CallObjectMethod(env, resolver, openInputStream, uri);
- jthrowable loadErr = (*env)->ExceptionOccurred(env);
- if (loadErr != NULL) {
- (*env)->ExceptionClear(env);
- return NULL;
- }
- return (*env)->NewGlobalRef(env, stream);
- }
- void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject resolver = getContentResolver(jni_env, ctx);
- jclass resolverClass = (*env)->GetObjectClass(env, resolver);
- jmethodID saveOutputStream = find_method(env, resolverClass, "openOutputStream", "(Landroid/net/Uri;Ljava/lang/String;)Ljava/io/OutputStream;");
- jobject uri = parseURI(jni_env, ctx, uriCstr);
- jstring modes = (*env)->NewStringUTF(env, "wt"); // truncate before write
- jobject stream = (jobject)(*env)->CallObjectMethod(env, resolver, saveOutputStream, uri, modes);
- jthrowable loadErr = (*env)->ExceptionOccurred(env);
- if (loadErr != NULL) {
- (*env)->ExceptionClear(env);
- return NULL;
- }
- return (*env)->NewGlobalRef(env, stream);
- }
- jbyte* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jclass streamClass = (*env)->GetObjectClass(env, stream);
- jmethodID read = find_method(env, streamClass, "read", "([BII)I");
- jbyteArray data = (*env)->NewByteArray(env, len);
- int count = (int)(*env)->CallIntMethod(env, stream, read, data, 0, len);
- *total = count;
- if (count == -1) {
- return NULL;
- }
- jbyte* bytes = (jbyte*)malloc(sizeof(jbyte)*count);
- (*env)->GetByteArrayRegion(env, data, 0, count, bytes);
- return bytes;
- }
- void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, jbyte* buf, int len) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jclass streamClass = (*env)->GetObjectClass(env, stream);
- jmethodID write = find_method(env, streamClass, "write", "([BII)V");
- jbyteArray data = (*env)->NewByteArray(env, len);
- (*env)->SetByteArrayRegion(env, data, 0, len, buf);
- (*env)->CallVoidMethod(env, stream, write, data, 0, len);
- free(buf);
- }
- void closeStream(uintptr_t jni_env, uintptr_t ctx, void* stream) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jclass streamClass = (*env)->GetObjectClass(env, stream);
- jmethodID close = find_method(env, streamClass, "close", "()V");
- (*env)->CallVoidMethod(env, stream, close);
- (*env)->DeleteGlobalRef(env, stream);
- }
- bool hasPrefix(char* string, char* prefix) {
- size_t lp = strlen(prefix);
- size_t ls = strlen(string);
- if (ls < lp) {
- return false;
- }
- return memcmp(prefix, string, lp) == 0;
- }
- bool canListContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject resolver = getContentResolver(jni_env, ctx);
- jobject uri = parseURI(jni_env, ctx, uriCstr);
- jthrowable loadErr = (*env)->ExceptionOccurred(env);
- if (loadErr != NULL) {
- (*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);
- if (type == NULL) {
- return false;
- }
- const char *str = getString(jni_env, ctx, type);
- return strcmp(str, "vnd.android.document/directory") == 0;
- }
- bool canListFileURI(char* uriCstr) {
- // 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';
- // Stat path to determine if it points to a directory
- struct stat statbuf;
- int result = stat(path, &statbuf);
- free(path);
- return (result == 0) && S_ISDIR(statbuf.st_mode);
- }
- bool canListURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- if (hasPrefix(uriCstr, "file://")) {
- return canListFileURI(uriCstr);
- } else if (hasPrefix(uriCstr, "content://")) {
- return canListContentURI(jni_env, ctx, uriCstr);
- }
- LOG_FATAL("Unrecognized scheme: %s", uriCstr);
- return false;
- }
- bool createListableFileURI(char* uriCstr) {
- // 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';
- int result = mkdir(path, S_IRWXU);
- free(path);
- return result == 0;
- }
- bool createListableURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- if (hasPrefix(uriCstr, "file://")) {
- return createListableFileURI(uriCstr);
- }
- LOG_FATAL("Cannot create directory for scheme: %s", uriCstr);
- return false;
- }
- const char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject resolver = getContentResolver(jni_env, ctx);
- jobject uri = parseURI(jni_env, ctx, uriCstr);
- jthrowable loadErr = (*env)->ExceptionOccurred(env);
- if (loadErr != NULL) {
- (*env)->ExceptionClear(env);
- return "";
- }
- jclass stringClass = find_class(env, "java/lang/String");
- jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "_display_name"));
- jclass resolverClass = (*env)->GetObjectClass(env, resolver);
- jmethodID query = find_method(env, resolverClass, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;");
- jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, uri, project, NULL, NULL, NULL);
- jclass cursorClass = (*env)->GetObjectClass(env, cursor);
- jmethodID first = find_method(env, cursorClass, "moveToFirst", "()Z");
- jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");
- if (((jboolean)(*env)->CallBooleanMethod(env, cursor, first)) == JNI_TRUE) {
- jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
- const char *fname = getString(jni_env, ctx, name);
- return fname;
- }
- return NULL;
- }
- bool existsFileURI(char* uriCstr) {
- // 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';
- // Stat path to determine if it points to an existing file
- struct stat statbuf;
- int result = stat(path, &statbuf);
- free(path);
- return result == 0;
- }
- bool existsURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- if (hasPrefix(uriCstr, "file://")) {
- return existsFileURI(uriCstr);
- }
- LOG_FATAL("Cannot check exists for scheme: %s", uriCstr);
- return false;
- }
- char* listContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- JNIEnv *env = (JNIEnv*)jni_env;
- jobject resolver = getContentResolver(jni_env, ctx);
- jobject uri = parseURI(jni_env, ctx, uriCstr);
- jthrowable loadErr = (*env)->ExceptionOccurred(env);
- if (loadErr != NULL) {
- (*env)->ExceptionClear(env);
- 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);
- 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 "";
- }
- 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;");
- jmethodID getChildURI = find_static_method(env, contractClass, "buildDocumentUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
- char *ret = NULL;
- int len = 0;
- while (((jboolean)(*env)->CallBooleanMethod(env, cursor, next)) == JNI_TRUE) {
- jstring childDocId = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
- jobject childUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChildURI, uri, childDocId);
- jclass uriClass = (*env)->GetObjectClass(env, childUri);
- jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;");
- jstring s = (jstring)(*env)->CallObjectMethod(env, childUri, toString);
- const char *uid = getString(jni_env, ctx, s);
- // append
- char *old = ret;
- len = len + strlen(uid) + 1;
- ret = malloc(sizeof(char)*(len+1));
- if (old != NULL) {
- strcpy(ret, old);
- free(old);
- } else {
- ret[0] = '\0';
- }
- strcat(ret, uid);
- strcat(ret, "|");
- }
- if (ret != NULL) {
- ret[len-1] = '\0';
- }
- return ret;
- }
- char* listFileURI(char* uriCstr) {
- size_t uriLength = strlen(uriCstr);
- // Get file path from URI
- size_t length = uriLength-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 + uriLength + 1 /* / */ + strlen(dp->d_name) + 1 /* | */;
- ret = malloc(sizeof(char)*(len+1));
- if (old != NULL) {
- strcpy(ret, old);
- free(old);
- } else {
- ret[0] = '\0';
- }
- strcat(ret, uriCstr);
- strcat(ret, "/");
- strcat(ret, dp->d_name);
- strcat(ret, "|");
- }
- if (ret != NULL) {
- ret[len-1] = '\0';
- }
- }
- free(path);
- return ret;
- }
- char* listURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
- if (hasPrefix(uriCstr, "file://")) {
- return listFileURI(uriCstr);
- } else if (hasPrefix(uriCstr, "content://")) {
- return listContentURI(jni_env, ctx, uriCstr);
- }
- LOG_FATAL("Unrecognized scheme: %s", uriCstr);
- return "";
- }
|