android.rs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2025 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use miniquad::native::android::{self, ndk_sys, ndk_utils};
  19. use parking_lot::Mutex as SyncMutex;
  20. use std::{collections::HashMap, path::PathBuf, sync::LazyLock};
  21. use crate::AndroidSuggestEvent;
  22. macro_rules! call_mainactivity_int_method {
  23. ($method:expr, $sig:expr $(, $args:expr)*) => {{
  24. unsafe {
  25. let env = android::attach_jni_env();
  26. ndk_utils::call_int_method!(env, android::ACTIVITY, $method, $sig $(, $args)*)
  27. }
  28. }};
  29. }
  30. macro_rules! call_mainactivity_str_method {
  31. ($method:expr) => {{
  32. unsafe {
  33. let env = android::attach_jni_env();
  34. let text = ndk_utils::call_object_method!(
  35. env,
  36. android::ACTIVITY,
  37. $method,
  38. "()Ljava/lang/String;"
  39. );
  40. ndk_utils::get_utf_str!(env, text)
  41. }
  42. }};
  43. }
  44. macro_rules! call_mainactivity_float_method {
  45. ($method:expr) => {{
  46. unsafe {
  47. let env = android::attach_jni_env();
  48. ndk_utils::call_method!(CallFloatMethod, env, android::ACTIVITY, $method, "()F")
  49. }
  50. }};
  51. }
  52. struct GlobalData {
  53. senders: HashMap<usize, async_channel::Sender<AndroidSuggestEvent>>,
  54. next_id: usize,
  55. }
  56. fn send(id: usize, ev: AndroidSuggestEvent) {
  57. let globals = &GLOBALS.lock();
  58. let Some(sender) = globals.senders.get(&id) else {
  59. warn!(target: "android", "Unknown composer_id={id} discard ev: {ev:?}");
  60. return
  61. };
  62. let _ = sender.try_send(ev);
  63. }
  64. unsafe impl Send for GlobalData {}
  65. unsafe impl Sync for GlobalData {}
  66. static GLOBALS: LazyLock<SyncMutex<GlobalData>> =
  67. LazyLock::new(|| SyncMutex::new(GlobalData { senders: HashMap::new(), next_id: 0 }));
  68. #[no_mangle]
  69. pub unsafe extern "C" fn Java_darkfi_darkfi_1app_MainActivity_onInitEdit(
  70. _env: *mut ndk_sys::JNIEnv,
  71. _: ndk_sys::jobject,
  72. id: ndk_sys::jint,
  73. ) {
  74. assert!(id >= 0);
  75. let id = id as usize;
  76. send(id, AndroidSuggestEvent::Init);
  77. }
  78. #[no_mangle]
  79. pub unsafe extern "C" fn Java_autosuggest_InvisibleInputView_onCreateInputConnect(
  80. _env: *mut ndk_sys::JNIEnv,
  81. _: ndk_sys::jobject,
  82. id: ndk_sys::jint,
  83. ) {
  84. assert!(id >= 0);
  85. let id = id as usize;
  86. send(id, AndroidSuggestEvent::CreateInputConnect);
  87. }
  88. #[no_mangle]
  89. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCompose(
  90. env: *mut ndk_sys::JNIEnv,
  91. _: ndk_sys::jobject,
  92. id: ndk_sys::jint,
  93. text: ndk_sys::jobject,
  94. cursor_pos: ndk_sys::jint,
  95. is_commit: ndk_sys::jboolean,
  96. ) {
  97. assert!(id >= 0);
  98. let id = id as usize;
  99. let text = ndk_utils::get_utf_str!(env, text);
  100. send(
  101. id,
  102. AndroidSuggestEvent::Compose {
  103. text: text.to_string(),
  104. cursor_pos,
  105. is_commit: is_commit == 1,
  106. },
  107. );
  108. }
  109. #[no_mangle]
  110. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onSetComposeRegion(
  111. _env: *mut ndk_sys::JNIEnv,
  112. _: ndk_sys::jobject,
  113. id: ndk_sys::jint,
  114. start: ndk_sys::jint,
  115. end: ndk_sys::jint,
  116. ) {
  117. assert!(id >= 0);
  118. let id = id as usize;
  119. send(id, AndroidSuggestEvent::ComposeRegion { start: start as usize, end: end as usize });
  120. }
  121. #[no_mangle]
  122. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onFinishCompose(
  123. _env: *mut ndk_sys::JNIEnv,
  124. _: ndk_sys::jobject,
  125. id: ndk_sys::jint,
  126. ) {
  127. assert!(id >= 0);
  128. let id = id as usize;
  129. send(id, AndroidSuggestEvent::FinishCompose);
  130. }
  131. #[no_mangle]
  132. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onDeleteSurroundingText(
  133. _env: *mut ndk_sys::JNIEnv,
  134. _: ndk_sys::jobject,
  135. id: ndk_sys::jint,
  136. left: ndk_sys::jint,
  137. right: ndk_sys::jint,
  138. ) {
  139. assert!(id >= 0);
  140. let id = id as usize;
  141. send(
  142. id,
  143. AndroidSuggestEvent::DeleteSurroundingText { left: left as usize, right: right as usize },
  144. );
  145. }
  146. pub fn create_composer(sender: async_channel::Sender<AndroidSuggestEvent>) -> usize {
  147. let composer_id = {
  148. let mut globals = GLOBALS.lock();
  149. let id = globals.next_id;
  150. globals.next_id += 1;
  151. globals.senders.insert(id, sender);
  152. id
  153. };
  154. unsafe {
  155. let env = android::attach_jni_env();
  156. ndk_utils::call_void_method!(env, android::ACTIVITY, "createComposer", "(I)V", composer_id);
  157. }
  158. composer_id
  159. }
  160. pub fn focus(id: usize) -> Option<()> {
  161. let is_success = unsafe {
  162. let env = android::attach_jni_env();
  163. ndk_utils::call_bool_method!(env, android::ACTIVITY, "focus", "(I)Z", id as i32)
  164. };
  165. if is_success == 0u8 {
  166. None
  167. } else {
  168. Some(())
  169. }
  170. }
  171. pub fn unfocus(id: usize) -> Option<()> {
  172. let is_success = unsafe {
  173. let env = android::attach_jni_env();
  174. ndk_utils::call_bool_method!(env, android::ACTIVITY, "unfocus", "(I)Z", id as i32)
  175. };
  176. if is_success == 0u8 {
  177. None
  178. } else {
  179. Some(())
  180. }
  181. }
  182. pub fn set_text(id: usize, text: &str) -> Option<()> {
  183. let ctext = std::ffi::CString::new(text).unwrap();
  184. let is_success = unsafe {
  185. let env = android::attach_jni_env();
  186. let new_string_utf = (**env).NewStringUTF.unwrap();
  187. let jtext = new_string_utf(env, ctext.as_ptr());
  188. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  189. let res = ndk_utils::call_bool_method!(
  190. env,
  191. android::ACTIVITY,
  192. "setText",
  193. "(ILjava/lang/String;)Z",
  194. id as i32,
  195. jtext
  196. );
  197. delete_local_ref(env, jtext);
  198. res
  199. };
  200. if is_success == 0u8 {
  201. None
  202. } else {
  203. Some(())
  204. }
  205. }
  206. pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
  207. //trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
  208. let is_success = unsafe {
  209. let env = android::attach_jni_env();
  210. ndk_utils::call_bool_method!(
  211. env,
  212. android::ACTIVITY,
  213. "setSelection",
  214. "(III)Z",
  215. id as i32,
  216. select_start as i32,
  217. select_end as i32
  218. )
  219. };
  220. if is_success == 0u8 {
  221. None
  222. } else {
  223. Some(())
  224. }
  225. }
  226. pub fn commit_text(id: usize, text: &str) -> Option<()> {
  227. let ctext = std::ffi::CString::new(text).unwrap();
  228. let is_success = unsafe {
  229. let env = android::attach_jni_env();
  230. let new_string_utf = (**env).NewStringUTF.unwrap();
  231. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  232. let jtext = new_string_utf(env, ctext.as_ptr());
  233. let res = ndk_utils::call_bool_method!(
  234. env,
  235. android::ACTIVITY,
  236. "commitText",
  237. "(ILjava/lang/String;)Z",
  238. id as i32,
  239. jtext
  240. );
  241. delete_local_ref(env, jtext);
  242. res
  243. };
  244. if is_success == 0u8 {
  245. None
  246. } else {
  247. Some(())
  248. }
  249. }
  250. pub struct Editable {
  251. pub buffer: String,
  252. pub select_start: usize,
  253. pub select_end: usize,
  254. pub compose_start: Option<usize>,
  255. pub compose_end: Option<usize>,
  256. }
  257. pub fn get_editable(id: usize) -> Option<Editable> {
  258. //trace!(target: "android", "get_editable({id})");
  259. unsafe {
  260. let env = android::attach_jni_env();
  261. let input_view = ndk_utils::call_object_method!(
  262. env,
  263. android::ACTIVITY,
  264. "getInputView",
  265. "(I)Lautosuggest/InvisibleInputView;",
  266. id as i32
  267. );
  268. if input_view.is_null() {
  269. return None
  270. }
  271. let buffer =
  272. ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
  273. assert!(!buffer.is_null());
  274. let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
  275. let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
  276. let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
  277. let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
  278. let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
  279. assert!(select_start >= 0);
  280. assert!(select_end >= 0);
  281. assert!(compose_start >= 0 || compose_start == compose_end);
  282. assert!(compose_start <= compose_end);
  283. Some(Editable {
  284. buffer,
  285. select_start: select_start as usize,
  286. select_end: select_end as usize,
  287. compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
  288. compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
  289. })
  290. }
  291. }
  292. pub fn get_appdata_path() -> PathBuf {
  293. call_mainactivity_str_method!("getAppDataPath").into()
  294. }
  295. pub fn get_external_storage_path() -> PathBuf {
  296. call_mainactivity_str_method!("getExternalStoragePath").into()
  297. }
  298. pub fn get_keyboard_height() -> usize {
  299. call_mainactivity_int_method!("getKeyboardHeight", "()I") as usize
  300. }
  301. pub fn get_screen_density() -> f32 {
  302. call_mainactivity_float_method!("getScreenDensity")
  303. }