android.rs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. ndk_utils::call_bool_method!(
  189. env,
  190. android::ACTIVITY,
  191. "setText",
  192. "(ILjava/lang/String;)Z",
  193. id as i32,
  194. jtext
  195. )
  196. };
  197. if is_success == 0u8 {
  198. None
  199. } else {
  200. Some(())
  201. }
  202. }
  203. pub fn set_selection(id: usize, select_start: usize, select_end: usize) -> Option<()> {
  204. //trace!(target: "android", "set_selection({id}, {select_start}, {select_end})");
  205. let is_success = unsafe {
  206. let env = android::attach_jni_env();
  207. ndk_utils::call_bool_method!(
  208. env,
  209. android::ACTIVITY,
  210. "setSelection",
  211. "(III)Z",
  212. id as i32,
  213. select_start as i32,
  214. select_end as i32
  215. )
  216. };
  217. if is_success == 0u8 {
  218. None
  219. } else {
  220. Some(())
  221. }
  222. }
  223. pub struct Editable {
  224. pub buffer: String,
  225. pub select_start: usize,
  226. pub select_end: usize,
  227. pub compose_start: Option<usize>,
  228. pub compose_end: Option<usize>,
  229. }
  230. pub fn get_editable(id: usize) -> Option<Editable> {
  231. //trace!(target: "android", "get_editable({id})");
  232. unsafe {
  233. let env = android::attach_jni_env();
  234. let input_view = ndk_utils::call_object_method!(
  235. env,
  236. android::ACTIVITY,
  237. "getInputView",
  238. "(I)Lautosuggest/InvisibleInputView;",
  239. id as i32
  240. );
  241. if input_view.is_null() {
  242. return None
  243. }
  244. let buffer =
  245. ndk_utils::call_object_method!(env, input_view, "rawText", "()Ljava/lang/String;");
  246. assert!(!buffer.is_null());
  247. let buffer = ndk_utils::get_utf_str!(env, buffer).to_string();
  248. let select_start = ndk_utils::call_int_method!(env, input_view, "getSelectionStart", "()I");
  249. let select_end = ndk_utils::call_int_method!(env, input_view, "getSelectionEnd", "()I");
  250. let compose_start = ndk_utils::call_int_method!(env, input_view, "getComposeStart", "()I");
  251. let compose_end = ndk_utils::call_int_method!(env, input_view, "getComposeEnd", "()I");
  252. assert!(select_start >= 0);
  253. assert!(select_end >= 0);
  254. assert!(compose_start >= 0 || compose_start == compose_end);
  255. assert!(compose_start <= compose_end);
  256. Some(Editable {
  257. buffer,
  258. select_start: select_start as usize,
  259. select_end: select_end as usize,
  260. compose_start: if compose_start < 0 { None } else { Some(compose_start as usize) },
  261. compose_end: if compose_end < 0 { None } else { Some(compose_end as usize) },
  262. })
  263. }
  264. }
  265. pub fn get_appdata_path() -> PathBuf {
  266. call_mainactivity_str_method!("getAppDataPath").into()
  267. }
  268. pub fn get_external_storage_path() -> PathBuf {
  269. call_mainactivity_str_method!("getExternalStoragePath").into()
  270. }
  271. pub fn get_keyboard_height() -> usize {
  272. call_mainactivity_int_method!("getKeyboardHeight", "()I") as usize
  273. }
  274. pub fn get_screen_density() -> f32 {
  275. call_mainactivity_float_method!("getScreenDensity")
  276. }