adam-gui/vendor/fyne.io/fyne/v2/internal/driver/mobile/android.c
2024-04-29 19:13:50 +02:00

508 lines
16 KiB
C

//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;
}
char *filePath(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';
return path;
}
bool deleteFileURI(char *uriCstr) {
char* path = filePath(uriCstr);
int result = remove(path);
free(path);
return result == 0;
}
bool deleteURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
if (!hasPrefix(uriCstr, "file://")) {
LOG_FATAL("Cannot delete for scheme: %s", uriCstr);
return false;
}
return deleteFileURI(uriCstr);
}
bool existsFileURI(char* uriCstr) {
char* path = filePath(uriCstr);
// 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 "";
}