android.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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. macro_rules! call_mainactivity_bool_method {
  53. ($method:expr) => {{
  54. unsafe {
  55. let env = android::attach_jni_env();
  56. ndk_utils::call_method!(CallBooleanMethod, env, android::ACTIVITY, $method, "()Z") !=
  57. 0u8
  58. }
  59. }};
  60. }
  61. struct GlobalData {
  62. senders: HashMap<usize, async_channel::Sender<AndroidSuggestEvent>>,
  63. next_id: usize,
  64. }
  65. fn send(id: usize, ev: AndroidSuggestEvent) {
  66. let globals = &GLOBALS.lock();
  67. let Some(sender) = globals.senders.get(&id) else {
  68. warn!(target: "android", "Unknown composer_id={id} discard ev: {ev:?}");
  69. return
  70. };
  71. let _ = sender.try_send(ev);
  72. }
  73. unsafe impl Send for GlobalData {}
  74. unsafe impl Sync for GlobalData {}
  75. static GLOBALS: LazyLock<SyncMutex<GlobalData>> =
  76. LazyLock::new(|| SyncMutex::new(GlobalData { senders: HashMap::new(), next_id: 0 }));
  77. #[no_mangle]
  78. pub unsafe extern "C" fn Java_darkfi_darkfi_1app_MainActivity_onInitEdit(
  79. _env: *mut ndk_sys::JNIEnv,
  80. _: ndk_sys::jobject,
  81. id: ndk_sys::jint,
  82. ) {
  83. assert!(id >= 0);
  84. let id = id as usize;
  85. send(id, AndroidSuggestEvent::Init);
  86. }
  87. #[no_mangle]
  88. pub unsafe extern "C" fn Java_autosuggest_InvisibleInputView_onCreateInputConnect(
  89. _env: *mut ndk_sys::JNIEnv,
  90. _: ndk_sys::jobject,
  91. id: ndk_sys::jint,
  92. ) {
  93. assert!(id >= 0);
  94. let id = id as usize;
  95. send(id, AndroidSuggestEvent::CreateInputConnect);
  96. }
  97. #[no_mangle]
  98. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onCompose(
  99. env: *mut ndk_sys::JNIEnv,
  100. _: ndk_sys::jobject,
  101. id: ndk_sys::jint,
  102. text: ndk_sys::jobject,
  103. cursor_pos: ndk_sys::jint,
  104. is_commit: ndk_sys::jboolean,
  105. ) {
  106. assert!(id >= 0);
  107. let id = id as usize;
  108. let text = ndk_utils::get_utf_str!(env, text);
  109. send(
  110. id,
  111. AndroidSuggestEvent::Compose {
  112. text: text.to_string(),
  113. cursor_pos,
  114. is_commit: is_commit == 1,
  115. },
  116. );
  117. }
  118. #[no_mangle]
  119. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onSetComposeRegion(
  120. _env: *mut ndk_sys::JNIEnv,
  121. _: ndk_sys::jobject,
  122. id: ndk_sys::jint,
  123. start: ndk_sys::jint,
  124. end: ndk_sys::jint,
  125. ) {
  126. assert!(id >= 0);
  127. let id = id as usize;
  128. send(id, AndroidSuggestEvent::ComposeRegion { start: start as usize, end: end as usize });
  129. }
  130. #[no_mangle]
  131. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onFinishCompose(
  132. _env: *mut ndk_sys::JNIEnv,
  133. _: ndk_sys::jobject,
  134. id: ndk_sys::jint,
  135. ) {
  136. assert!(id >= 0);
  137. let id = id as usize;
  138. send(id, AndroidSuggestEvent::FinishCompose);
  139. }
  140. #[no_mangle]
  141. pub unsafe extern "C" fn Java_autosuggest_CustomInputConnection_onDeleteSurroundingText(
  142. _env: *mut ndk_sys::JNIEnv,
  143. _: ndk_sys::jobject,
  144. id: ndk_sys::jint,
  145. left: ndk_sys::jint,
  146. right: ndk_sys::jint,
  147. ) {
  148. assert!(id >= 0);
  149. let id = id as usize;
  150. send(
  151. id,
  152. AndroidSuggestEvent::DeleteSurroundingText { left: left as usize, right: right as usize },
  153. );
  154. }
  155. pub fn create_composer(sender: async_channel::Sender<AndroidSuggestEvent>) -> usize {
  156. let composer_id = {
  157. let mut globals = GLOBALS.lock();
  158. let id = globals.next_id;
  159. globals.next_id += 1;
  160. globals.senders.insert(id, sender);
  161. id
  162. };
  163. unsafe {
  164. let env = android::attach_jni_env();
  165. ndk_utils::call_void_method!(env, android::ACTIVITY, "createComposer", "(I)V", composer_id);
  166. }
  167. composer_id
  168. }
  169. pub fn focus(id: usize) -> Option<()> {
  170. let is_success = unsafe {
  171. let env = android::attach_jni_env();
  172. ndk_utils::call_bool_method!(env, android::ACTIVITY, "focus", "(I)Z", id as i32)
  173. };
  174. if is_success == 0u8 {
  175. None
  176. } else {
  177. Some(())
  178. }
  179. }
  180. pub fn unfocus(id: usize) -> Option<()> {
  181. let is_success = unsafe {
  182. let env = android::attach_jni_env();
  183. ndk_utils::call_bool_method!(env, android::ACTIVITY, "unfocus", "(I)Z", id as i32)
  184. };
  185. if is_success == 0u8 {
  186. None
  187. } else {
  188. Some(())
  189. }
  190. }
  191. pub fn set_text(id: usize, text: &str) -> Option<()> {
  192. let ctext = std::ffi::CString::new(text).unwrap();
  193. let is_success = unsafe {
  194. let env = android::attach_jni_env();
  195. let new_string_utf = (**env).NewStringUTF.unwrap();
  196. let jtext = new_string_utf(env, ctext.as_ptr());
  197. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  198. let res = ndk_utils::call_bool_method!(
  199. env,
  200. android::ACTIVITY,
  201. "setText",
  202. "(ILjava/lang/String;)Z",
  203. id as i32,
  204. jtext
  205. );
  206. delete_local_ref(env, jtext);
  207. res
  208. };
  209. if is_success == 0u8 {
  210. None
  211. } else {
  212. Some(())
  213. }
  214. }
  215. pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
  216. //trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
  217. let is_success = unsafe {
  218. let env = android::attach_jni_env();
  219. ndk_utils::call_bool_method!(
  220. env,
  221. android::ACTIVITY,
  222. "setSelection",
  223. "(III)Z",
  224. id as i32,
  225. select_start as i32,
  226. select_end as i32
  227. )
  228. };
  229. if is_success == 0u8 {
  230. None
  231. } else {
  232. Some(())
  233. }
  234. }
  235. pub fn commit_text(id: usize, text: &str) -> Option<()> {
  236. let ctext = std::ffi::CString::new(text).unwrap();
  237. let is_success = unsafe {
  238. let env = android::attach_jni_env();
  239. let new_string_utf = (**env).NewStringUTF.unwrap();
  240. let delete_local_ref = (**env).DeleteLocalRef.unwrap();
  241. let jtext = new_string_utf(env, ctext.as_ptr());
  242. let res = ndk_utils::call_bool_method!(
  243. env,
  244. android::ACTIVITY,
  245. "commitText",
  246. "(ILjava/lang/String;)Z",
  247. id as i32,
  248. jtext
  249. );
  250. delete_local_ref(env, jtext);
  251. res
  252. };
  253. if is_success == 0u8 {
  254. None
  255. } else {
  256. Some(())
  257. }
  258. }
  259. pub struct Editable {
  260. pub buffer: String,
  261. pub select_start: usize,
  262. pub select_end: usize,
  263. pub compose_start: Option<usize>,
  264. pub compose_end: Option<usize>,
  265. }
  266. pub fn get_editable(id: usize) -> Option<Editable> {
  267. //trace!(target: "android", "get_editable({id})");
  268. unsafe {
  269. let env = android::attach_jni_env();
  270. let input_view = ndk_utils::call_object_method!(
  271. env,
  272. android::ACTIVITY,
  273. "getInputView",
  274. "(I)Lautosuggest/InvisibleInputView;",
  275. id as i32
  276. );
  277. if input_view.is_null() {
  278. return None
  279. }
  280. let buffer =
  281. ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
  282. assert!(!buffer.is_null());
  283. let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
  284. let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
  285. let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
  286. let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
  287. let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
  288. assert!(select_start >= 0);
  289. assert!(select_end >= 0);
  290. assert!(compose_start >= 0 || compose_start == compose_end);
  291. assert!(compose_start <= compose_end);
  292. Some(Editable {
  293. buffer,
  294. select_start: select_start as usize,
  295. select_end: select_end as usize,
  296. compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
  297. compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
  298. })
  299. }
  300. }
  301. pub fn get_appdata_path() -> PathBuf {
  302. call_mainactivity_str_method!("getAppDataPath").into()
  303. }
  304. pub fn get_external_storage_path() -> PathBuf {
  305. call_mainactivity_str_method!("getExternalStoragePath").into()
  306. }
  307. pub fn get_keyboard_height() -> usize {
  308. call_mainactivity_int_method!("getKeyboardHeight", "()I") as usize
  309. }
  310. pub fn get_screen_density() -> f32 {
  311. call_mainactivity_float_method!("getScreenDensity")
  312. }
  313. pub fn is_ime_visible() -> bool {
  314. call_mainactivity_bool_method!("isImeVisible")
  315. }