android.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. //go:build android
  2. // +build android
  3. #include <android/log.h>
  4. #include <dirent.h>
  5. #include <jni.h>
  6. #include <stdbool.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <sys/stat.h>
  10. #define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Fyne", __VA_ARGS__)
  11. static jclass find_class(JNIEnv *env, const char *class_name) {
  12. jclass clazz = (*env)->FindClass(env, class_name);
  13. if (clazz == NULL) {
  14. (*env)->ExceptionClear(env);
  15. LOG_FATAL("cannot find %s", class_name);
  16. return NULL;
  17. }
  18. return clazz;
  19. }
  20. static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
  21. jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
  22. if (m == 0) {
  23. (*env)->ExceptionClear(env);
  24. LOG_FATAL("cannot find method %s %s", name, sig);
  25. return 0;
  26. }
  27. return m;
  28. }
  29. static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
  30. jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig);
  31. if (m == 0) {
  32. (*env)->ExceptionClear(env);
  33. LOG_FATAL("cannot find method %s %s", name, sig);
  34. return 0;
  35. }
  36. return m;
  37. }
  38. char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) {
  39. JNIEnv *env = (JNIEnv*)jni_env;
  40. const char *chars = (*env)->GetStringUTFChars(env, str, NULL);
  41. const char *copy = strdup(chars);
  42. (*env)->ReleaseStringUTFChars(env, str, chars);
  43. return copy;
  44. }
  45. jobject parseURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  46. JNIEnv *env = (JNIEnv*)jni_env;
  47. jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
  48. jclass uriClass = find_class(env, "android/net/Uri");
  49. jmethodID parse = find_static_method(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
  50. return (jobject)(*env)->CallStaticObjectMethod(env, uriClass, parse, uriStr);
  51. }
  52. // clipboard
  53. jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) {
  54. JNIEnv *env = (JNIEnv*)jni_env;
  55. jclass ctxClass = (*env)->GetObjectClass(env, ctx);
  56. jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
  57. jstring service = (*env)->NewStringUTF(env, "clipboard");
  58. jobject ret = (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, service);
  59. jthrowable err = (*env)->ExceptionOccurred(env);
  60. if (err != NULL) {
  61. LOG_FATAL("cannot lookup clipboard");
  62. (*env)->ExceptionClear(env);
  63. return NULL;
  64. }
  65. return ret;
  66. }
  67. char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
  68. JNIEnv *env = (JNIEnv*)jni_env;
  69. jobject mgr = getClipboard(jni_env, ctx);
  70. if (mgr == NULL) {
  71. return NULL;
  72. }
  73. jclass mgrClass = (*env)->GetObjectClass(env, mgr);
  74. jmethodID getText = find_method(env, mgrClass, "getText", "()Ljava/lang/CharSequence;");
  75. jobject content = (jstring)(*env)->CallObjectMethod(env, mgr, getText);
  76. if (content == NULL) {
  77. return NULL;
  78. }
  79. jclass clzCharSequence = (*env)->GetObjectClass(env, content);
  80. jmethodID toString = (*env)->GetMethodID(env, clzCharSequence, "toString", "()Ljava/lang/String;");
  81. jobject s = (*env)->CallObjectMethod(env, content, toString);
  82. return getString(jni_env, ctx, s);
  83. }
  84. void setClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *content) {
  85. JNIEnv *env = (JNIEnv*)jni_env;
  86. jobject mgr = getClipboard(jni_env, ctx);
  87. if (mgr == NULL) {
  88. return;
  89. }
  90. jclass mgrClass = (*env)->GetObjectClass(env, mgr);
  91. jmethodID setText = find_method(env, mgrClass, "setText", "(Ljava/lang/CharSequence;)V");
  92. jstring str = (*env)->NewStringUTF(env, content);
  93. (*env)->CallVoidMethod(env, mgr, setText, str);
  94. }
  95. // file handling
  96. jobject getContentResolver(uintptr_t jni_env, uintptr_t ctx) {
  97. JNIEnv *env = (JNIEnv*)jni_env;
  98. jclass ctxClass = (*env)->GetObjectClass(env, ctx);
  99. jmethodID getContentResolver = find_method(env, ctxClass, "getContentResolver", "()Landroid/content/ContentResolver;");
  100. return (jobject)(*env)->CallObjectMethod(env, ctx, getContentResolver);
  101. }
  102. void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  103. JNIEnv *env = (JNIEnv*)jni_env;
  104. jobject resolver = getContentResolver(jni_env, ctx);
  105. jclass resolverClass = (*env)->GetObjectClass(env, resolver);
  106. jmethodID openInputStream = find_method(env, resolverClass, "openInputStream", "(Landroid/net/Uri;)Ljava/io/InputStream;");
  107. jobject uri = parseURI(jni_env, ctx, uriCstr);
  108. jobject stream = (jobject)(*env)->CallObjectMethod(env, resolver, openInputStream, uri);
  109. jthrowable loadErr = (*env)->ExceptionOccurred(env);
  110. if (loadErr != NULL) {
  111. (*env)->ExceptionClear(env);
  112. return NULL;
  113. }
  114. return (*env)->NewGlobalRef(env, stream);
  115. }
  116. void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  117. JNIEnv *env = (JNIEnv*)jni_env;
  118. jobject resolver = getContentResolver(jni_env, ctx);
  119. jclass resolverClass = (*env)->GetObjectClass(env, resolver);
  120. jmethodID saveOutputStream = find_method(env, resolverClass, "openOutputStream", "(Landroid/net/Uri;Ljava/lang/String;)Ljava/io/OutputStream;");
  121. jobject uri = parseURI(jni_env, ctx, uriCstr);
  122. jstring modes = (*env)->NewStringUTF(env, "wt"); // truncate before write
  123. jobject stream = (jobject)(*env)->CallObjectMethod(env, resolver, saveOutputStream, uri, modes);
  124. jthrowable loadErr = (*env)->ExceptionOccurred(env);
  125. if (loadErr != NULL) {
  126. (*env)->ExceptionClear(env);
  127. return NULL;
  128. }
  129. return (*env)->NewGlobalRef(env, stream);
  130. }
  131. char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) {
  132. JNIEnv *env = (JNIEnv*)jni_env;
  133. jclass streamClass = (*env)->GetObjectClass(env, stream);
  134. jmethodID read = find_method(env, streamClass, "read", "([BII)I");
  135. jbyteArray data = (*env)->NewByteArray(env, len);
  136. int count = (int)(*env)->CallIntMethod(env, stream, read, data, 0, len);
  137. *total = count;
  138. if (count == -1) {
  139. return NULL;
  140. }
  141. char* bytes = malloc(sizeof(char)*count);
  142. (*env)->GetByteArrayRegion(env, data, 0, count, bytes);
  143. return bytes;
  144. }
  145. void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, char* buf, int len) {
  146. JNIEnv *env = (JNIEnv*)jni_env;
  147. jclass streamClass = (*env)->GetObjectClass(env, stream);
  148. jmethodID write = find_method(env, streamClass, "write", "([BII)V");
  149. jbyteArray data = (*env)->NewByteArray(env, len);
  150. (*env)->SetByteArrayRegion(env, data, 0, len, buf);
  151. (*env)->CallVoidMethod(env, stream, write, data, 0, len);
  152. free(buf);
  153. }
  154. void closeStream(uintptr_t jni_env, uintptr_t ctx, void* stream) {
  155. JNIEnv *env = (JNIEnv*)jni_env;
  156. jclass streamClass = (*env)->GetObjectClass(env, stream);
  157. jmethodID close = find_method(env, streamClass, "close", "()V");
  158. (*env)->CallVoidMethod(env, stream, close);
  159. (*env)->DeleteGlobalRef(env, stream);
  160. }
  161. bool hasPrefix(char* string, char* prefix) {
  162. size_t lp = strlen(prefix);
  163. size_t ls = strlen(string);
  164. if (ls < lp) {
  165. return false;
  166. }
  167. return memcmp(prefix, string, lp) == 0;
  168. }
  169. bool canListContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  170. JNIEnv *env = (JNIEnv*)jni_env;
  171. jobject resolver = getContentResolver(jni_env, ctx);
  172. jobject uri = parseURI(jni_env, ctx, uriCstr);
  173. jthrowable loadErr = (*env)->ExceptionOccurred(env);
  174. if (loadErr != NULL) {
  175. (*env)->ExceptionClear(env);
  176. return false;
  177. }
  178. jclass contractClass = find_class(env, "android/provider/DocumentsContract");
  179. if (contractClass == NULL) { // API 19
  180. return false;
  181. }
  182. jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
  183. if (getDoc == NULL) { // API 21
  184. return false;
  185. }
  186. jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);
  187. jmethodID getTree = find_static_method(env, contractClass, "buildDocumentUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
  188. jobject treeUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getTree, uri, docID);
  189. jclass resolverClass = (*env)->GetObjectClass(env, resolver);
  190. jmethodID getType = find_method(env, resolverClass, "getType", "(Landroid/net/Uri;)Ljava/lang/String;");
  191. jstring type = (jstring)(*env)->CallObjectMethod(env, resolver, getType, treeUri);
  192. if (type == NULL) {
  193. return false;
  194. }
  195. char *str = getString(jni_env, ctx, type);
  196. return strcmp(str, "vnd.android.document/directory") == 0;
  197. }
  198. bool canListFileURI(char* uriCstr) {
  199. // Get file path from URI
  200. size_t length = strlen(uriCstr)-7;// -7 for 'file://'
  201. char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
  202. memcpy(path, &uriCstr[7], length);
  203. path[length] = '\0';
  204. // Stat path to determine if it points to a directory
  205. struct stat statbuf;
  206. int result = stat(path, &statbuf);
  207. free(path);
  208. return (result == 0) && S_ISDIR(statbuf.st_mode);
  209. }
  210. bool canListURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  211. if (hasPrefix(uriCstr, "file://")) {
  212. return canListFileURI(uriCstr);
  213. } else if (hasPrefix(uriCstr, "content://")) {
  214. return canListContentURI(jni_env, ctx, uriCstr);
  215. }
  216. LOG_FATAL("Unrecognized scheme: %s", uriCstr);
  217. return false;
  218. }
  219. bool createListableFileURI(char* uriCstr) {
  220. // Get file path from URI
  221. size_t length = strlen(uriCstr)-7;// -7 for 'file://'
  222. char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
  223. memcpy(path, &uriCstr[7], length);
  224. path[length] = '\0';
  225. int result = mkdir(path, S_IRWXU);
  226. free(path);
  227. return result == 0;
  228. }
  229. bool createListableURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  230. if (hasPrefix(uriCstr, "file://")) {
  231. return createListableFileURI(uriCstr);
  232. }
  233. LOG_FATAL("Cannot create directory for scheme: %s", uriCstr);
  234. return false;
  235. }
  236. char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  237. JNIEnv *env = (JNIEnv*)jni_env;
  238. jobject resolver = getContentResolver(jni_env, ctx);
  239. jobject uri = parseURI(jni_env, ctx, uriCstr);
  240. jthrowable loadErr = (*env)->ExceptionOccurred(env);
  241. if (loadErr != NULL) {
  242. (*env)->ExceptionClear(env);
  243. return "";
  244. }
  245. jclass stringClass = find_class(env, "java/lang/String");
  246. jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "_display_name"));
  247. jclass resolverClass = (*env)->GetObjectClass(env, resolver);
  248. 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;");
  249. jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, uri, project, NULL, NULL, NULL);
  250. jclass cursorClass = (*env)->GetObjectClass(env, cursor);
  251. jmethodID first = find_method(env, cursorClass, "moveToFirst", "()Z");
  252. jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");
  253. if (((jboolean)(*env)->CallBooleanMethod(env, cursor, first)) == JNI_TRUE) {
  254. jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
  255. char *fname = getString(jni_env, ctx, name);
  256. return fname;
  257. }
  258. return NULL;
  259. }
  260. bool existsFileURI(char* uriCstr) {
  261. // Get file path from URI
  262. size_t length = strlen(uriCstr)-7;// -7 for 'file://'
  263. char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
  264. memcpy(path, &uriCstr[7], length);
  265. path[length] = '\0';
  266. // Stat path to determine if it points to an existing file
  267. struct stat statbuf;
  268. int result = stat(path, &statbuf);
  269. free(path);
  270. return result == 0;
  271. }
  272. bool existsURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  273. if (hasPrefix(uriCstr, "file://")) {
  274. return existsFileURI(uriCstr);
  275. }
  276. LOG_FATAL("Cannot check exists for scheme: %s", uriCstr);
  277. return false;
  278. }
  279. char* listContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  280. JNIEnv *env = (JNIEnv*)jni_env;
  281. jobject resolver = getContentResolver(jni_env, ctx);
  282. jobject uri = parseURI(jni_env, ctx, uriCstr);
  283. jthrowable loadErr = (*env)->ExceptionOccurred(env);
  284. if (loadErr != NULL) {
  285. (*env)->ExceptionClear(env);
  286. return "";
  287. }
  288. jclass contractClass = find_class(env, "android/provider/DocumentsContract");
  289. if (contractClass == NULL) { // API 19
  290. return "";
  291. }
  292. jmethodID getDoc = find_static_method(env, contractClass, "getTreeDocumentId", "(Landroid/net/Uri;)Ljava/lang/String;");
  293. if (getDoc == NULL) { // API 21
  294. return "";
  295. }
  296. jstring docID = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getDoc, uri);
  297. jmethodID getChild = find_static_method(env, contractClass, "buildChildDocumentsUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
  298. jobject childrenUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChild, uri, docID);
  299. jclass stringClass = find_class(env, "java/lang/String");
  300. jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "document_id"));
  301. jclass resolverClass = (*env)->GetObjectClass(env, resolver);
  302. jmethodID query = find_method(env, resolverClass, "query", "(Landroid/net/Uri;[Ljava/lang/String;Landroid/os/Bundle;Landroid/os/CancellationSignal;)Landroid/database/Cursor;");
  303. if (getDoc == NULL) { // API 26
  304. return "";
  305. }
  306. jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, childrenUri, project, NULL, NULL);
  307. jclass cursorClass = (*env)->GetObjectClass(env, cursor);
  308. jmethodID next = find_method(env, cursorClass, "moveToNext", "()Z");
  309. jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");
  310. jmethodID getChildURI = find_static_method(env, contractClass, "buildDocumentUriUsingTree", "(Landroid/net/Uri;Ljava/lang/String;)Landroid/net/Uri;");
  311. char *ret = NULL;
  312. int len = 0;
  313. while (((jboolean)(*env)->CallBooleanMethod(env, cursor, next)) == JNI_TRUE) {
  314. jstring childDocId = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
  315. jobject childUri = (jobject)(*env)->CallStaticObjectMethod(env, contractClass, getChildURI, uri, childDocId);
  316. jclass uriClass = (*env)->GetObjectClass(env, childUri);
  317. jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;");
  318. jstring s = (jstring)(*env)->CallObjectMethod(env, childUri, toString);
  319. char *uid = getString(jni_env, ctx, s);
  320. // append
  321. char *old = ret;
  322. len = len + strlen(uid) + 1;
  323. ret = malloc(sizeof(char)*(len+1));
  324. if (old != NULL) {
  325. strcpy(ret, old);
  326. free(old);
  327. } else {
  328. ret[0] = '\0';
  329. }
  330. strcat(ret, uid);
  331. strcat(ret, "|");
  332. }
  333. if (ret != NULL) {
  334. ret[len-1] = '\0';
  335. }
  336. return ret;
  337. }
  338. char* listFileURI(char* uriCstr) {
  339. size_t uriLength = strlen(uriCstr);
  340. // Get file path from URI
  341. size_t length = uriLength-7;// -7 for 'file://'
  342. char* path = malloc(sizeof(char)*(length+1));// +1 for '\0'
  343. memcpy(path, &uriCstr[7], length);
  344. path[length] = '\0';
  345. char *ret = NULL;
  346. DIR *dfd;
  347. if ((dfd = opendir(path)) != NULL) {
  348. struct dirent *dp;
  349. int len = 0;
  350. while ((dp = readdir(dfd)) != NULL) {
  351. if (strcmp(dp->d_name, ".") == 0) {
  352. // Ignore current directory
  353. continue;
  354. }
  355. if (strcmp(dp->d_name, "..") == 0) {
  356. // Ignore parent directory
  357. continue;
  358. }
  359. // append
  360. char *old = ret;
  361. len = len + uriLength + 1 /* / */ + strlen(dp->d_name) + 1 /* | */;
  362. ret = malloc(sizeof(char)*(len+1));
  363. if (old != NULL) {
  364. strcpy(ret, old);
  365. free(old);
  366. } else {
  367. ret[0] = '\0';
  368. }
  369. strcat(ret, uriCstr);
  370. strcat(ret, "/");
  371. strcat(ret, dp->d_name);
  372. strcat(ret, "|");
  373. }
  374. if (ret != NULL) {
  375. ret[len-1] = '\0';
  376. }
  377. }
  378. free(path);
  379. return ret;
  380. }
  381. char* listURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
  382. if (hasPrefix(uriCstr, "file://")) {
  383. return listFileURI(uriCstr);
  384. } else if (hasPrefix(uriCstr, "content://")) {
  385. return listContentURI(jni_env, ctx, uriCstr);
  386. }
  387. LOG_FATAL("Unrecognized scheme: %s", uriCstr);
  388. return "";
  389. }