From 09cecc40e69a757b3fe83e38d50651503d9339e9 Mon Sep 17 00:00:00 2001 From: Aaron Muir Hamilton Date: Wed, 10 Jul 2024 10:05:39 -0400 Subject: [PATCH 1/4] Android: Show keyboard with WindowInsetsController. --- Cargo.toml | 1 + src/changelog/unreleased.md | 1 + src/platform_impl/android/mod.rs | 43 ++++++++++++++++++++++++++++++-- src/window.rs | 3 ++- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e858087eb5..928cb1aa80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ softbuffer = { version = "0.4.0", default-features = false, features = [ [target.'cfg(target_os = "android")'.dependencies] android-activity = "0.6.0" ndk = { version = "0.9.0", default-features = false } +jni = "0.21" [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] core-foundation = "0.9.3" diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9350bb2f1f..ee46ec3ea6 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -43,6 +43,7 @@ changelog entry. ### Added - Reexport `raw-window-handle` versions 0.4 and 0.5 as `raw_window_handle_04` and `raw_window_handle_05`. +- On Android, `set_ime_allowed` now opens and closes the soft keyboard. ### Removed diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 499ad67e2e..0ebf04a34e 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -183,6 +183,7 @@ impl EventLoop { &redraw_flag, android_app.create_waker(), ), + show_keyboard_on_resume: Arc::new(AtomicBool::new(false)), }, _marker: PhantomData, }, @@ -267,6 +268,10 @@ impl EventLoop { MainEvent::Resume { .. } => { debug!("App Resumed - is running"); self.running = true; + show_hide_keyboard( + self.window_target.p.app.clone(), + self.window_target.p.show_keyboard_on_resume.load(Ordering::SeqCst), + ); }, MainEvent::SaveState { .. } => { // XXX: how to forward this state to applications? @@ -635,6 +640,7 @@ pub struct ActiveEventLoop { control_flow: Cell, exit: Cell, redraw_requester: RedrawRequester, + show_keyboard_on_resume: Arc, } impl ActiveEventLoop { @@ -747,9 +753,35 @@ impl DeviceId { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowAttributes; +fn show_hide_keyboard(app: AndroidApp, show: bool) { + use jni::{objects::JObject, JavaVM}; + let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as _).unwrap() }; + let activity = unsafe { JObject::from_raw(app.activity_as_ptr() as _) }; + let mut env = vm.attach_current_thread().unwrap(); + let window = env + .call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) + .unwrap() + .l() + .unwrap(); + let wic = env + .call_method(window, "getInsetsController", "()Landroid/view/WindowInsetsController;", &[]) + .unwrap() + .l() + .unwrap(); + let window_insets_types = env.find_class("android/view/WindowInsets$Type").unwrap(); + let ime_type = + env.call_static_method(&window_insets_types, "ime", "()I", &[]).unwrap().i().unwrap(); + let _ = if show { + env.call_method(&wic, "show", "(I)V", &[ime_type.into()]) + } else { + env.call_method(&wic, "hide", "(I)V", &[ime_type.into()]) + }; +} + pub(crate) struct Window { app: AndroidApp, redraw_requester: RedrawRequester, + show_keyboard_on_resume: Arc, } impl Window { @@ -759,7 +791,11 @@ impl Window { ) -> Result { // FIXME this ignores requested window attributes - Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() }) + Ok(Self { + app: el.app.clone(), + redraw_requester: el.redraw_requester.clone(), + show_keyboard_on_resume: el.show_keyboard_on_resume.clone(), + }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Self) + Send + 'static) { @@ -888,7 +924,10 @@ impl Window { pub fn set_ime_cursor_area(&self, _position: Position, _size: Size) {} - pub fn set_ime_allowed(&self, _allowed: bool) {} + pub fn set_ime_allowed(&self, allowed: bool) { + self.show_keyboard_on_resume.store(allowed, Ordering::SeqCst); + show_hide_keyboard(self.app.clone(), allowed); + } pub fn set_ime_purpose(&self, _purpose: ImePurpose) {} diff --git a/src/window.rs b/src/window.rs index a8888c9f75..9b019fab92 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1278,7 +1278,8 @@ impl Window { /// /// - **macOS:** IME must be enabled to receive text-input where dead-key sequences are /// combined. - /// - **iOS / Android / Web / Orbital:** Unsupported. + /// - **Android:** Enabling IME only summons the soft keyboard, does not enable IME + /// - **iOS / Web / Orbital:** Unsupported. /// - **X11**: Enabling IME will disable dead keys reporting during compose. /// /// [`Ime`]: crate::event::WindowEvent::Ime From f9906c9c49ec063f00e0d6f51d8f0429d75041db Mon Sep 17 00:00:00 2001 From: Aaron Muir Hamilton Date: Thu, 11 Jul 2024 09:55:43 -0400 Subject: [PATCH 2/4] Android: Split show_hide_keyboard into a fallible portion and trace. --- src/platform_impl/android/mod.rs | 33 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 0ebf04a34e..2ecd10ef7a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -753,28 +753,23 @@ impl DeviceId { #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct PlatformSpecificWindowAttributes; -fn show_hide_keyboard(app: AndroidApp, show: bool) { +fn show_hide_keyboard_fallible(app: AndroidApp, show: bool) -> Result<(), jni::errors::Error> { use jni::{objects::JObject, JavaVM}; - let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as _).unwrap() }; + let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as _)? }; let activity = unsafe { JObject::from_raw(app.activity_as_ptr() as _) }; - let mut env = vm.attach_current_thread().unwrap(); - let window = env - .call_method(&activity, "getWindow", "()Landroid/view/Window;", &[]) - .unwrap() - .l() - .unwrap(); + let mut env = vm.attach_current_thread()?; + let window = env.call_method(&activity, "getWindow", "()Landroid/view/Window;", &[])?.l()?; let wic = env - .call_method(window, "getInsetsController", "()Landroid/view/WindowInsetsController;", &[]) - .unwrap() - .l() - .unwrap(); - let window_insets_types = env.find_class("android/view/WindowInsets$Type").unwrap(); - let ime_type = - env.call_static_method(&window_insets_types, "ime", "()I", &[]).unwrap().i().unwrap(); - let _ = if show { - env.call_method(&wic, "show", "(I)V", &[ime_type.into()]) - } else { - env.call_method(&wic, "hide", "(I)V", &[ime_type.into()]) + .call_method(window, "getInsetsController", "()Landroid/view/WindowInsetsController;", &[])? + .l()?; + let window_insets_types = env.find_class("android/view/WindowInsets$Type")?; + let ime_type = env.call_static_method(&window_insets_types, "ime", "()I", &[])?.i()?; + env.call_method(&wic, if show { "show" } else { "hide" }, "(I)V", &[ime_type.into()])?.v() +} + +fn show_hide_keyboard(app: AndroidApp, show: bool) { + if let Err(e) = show_hide_keyboard_fallible(app, show) { + tracing::error!("Showing or hiding the soft keyboard failed: {e:?}"); }; } From 69f11e75177c736e3554d272b09fbb2804d21e0b Mon Sep 17 00:00:00 2001 From: Aaron Muir Hamilton Date: Thu, 11 Jul 2024 10:16:25 -0400 Subject: [PATCH 3/4] Android: Explain why we use `WindowInsetsController`. --- src/platform_impl/android/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 2ecd10ef7a..875c9ea4d9 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -754,6 +754,9 @@ impl DeviceId { pub struct PlatformSpecificWindowAttributes; fn show_hide_keyboard_fallible(app: AndroidApp, show: bool) -> Result<(), jni::errors::Error> { + // After Android R, it is no longer possible to show the soft keyboard + // with `showSoftInput` alone. + // Here we use `WindowInsetsController`, which is the other way. use jni::{objects::JObject, JavaVM}; let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as _)? }; let activity = unsafe { JObject::from_raw(app.activity_as_ptr() as _) }; From ff14be54a88c598d493e232afce4ed9c9a4e05a8 Mon Sep 17 00:00:00 2001 From: Aaron Muir Hamilton Date: Thu, 11 Jul 2024 15:52:00 -0400 Subject: [PATCH 4/4] Move use to top of file. --- src/platform_impl/android/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 875c9ea4d9..c8972b5f61 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -12,6 +12,7 @@ use android_activity::input::{InputEvent, KeyAction, Keycode, MotionAction}; use android_activity::{ AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, Rect, }; +use jni::{objects::JObject, JavaVM}; use tracing::{debug, trace, warn}; use crate::application::ApplicationHandler; @@ -757,7 +758,6 @@ fn show_hide_keyboard_fallible(app: AndroidApp, show: bool) -> Result<(), jni::e // After Android R, it is no longer possible to show the soft keyboard // with `showSoftInput` alone. // Here we use `WindowInsetsController`, which is the other way. - use jni::{objects::JObject, JavaVM}; let vm = unsafe { JavaVM::from_raw(app.vm_as_ptr() as _)? }; let activity = unsafe { JObject::from_raw(app.activity_as_ptr() as _) }; let mut env = vm.attach_current_thread()?;