Skip to content

Commit c3ed6ba

Browse files
jancespivorib
andcommitted
Use InputMethodManager#showSoftInput to show_soft_input
This updates `AndroidApp::show/hide_soft_input` to be implemented manually with JNI (instead of `ANativeActivity_show/hideSoftInput`) so that we can pass the root, decor view to `InputMethodManager.showSoftInput` instead of the private `mNativeContentView` created by `NativeActivity`. Unlike the private `mNativeContentView`, the root decor view is considered to be the current "served" view for a vanilla `NativeActivity`-based application. Co-authored-by: Robert Bragg <robert@sixbynine.org>
1 parent c1d00b9 commit c3ed6ba

6 files changed

Lines changed: 122 additions & 26 deletions

File tree

android-activity/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ fn android_on_create(state: &OnCreateState) {
6161
- *Safety* The `native-activity` backend clears its `ANativeActivity` ptr after `onDestroy` and `AndroidApp` remains safe to access after `android_main()` returns ([#234](https://github.com/rust-mobile/android-activity/pull/234))
6262
- *Safety* `AndroidApp::activity_as_ptr()` returns a pointer to a global reference that remains valid until `AndroidApp` is dropped, instead of the `ANativeActivity`'s `clazz` pointer which is only guaranteed to be valid until `onDestroy` returns (`native-activity` backend) ([#234](https://github.com/rust-mobile/android-activity/pull/234))
6363
- *Safety* The `game-activity` backend clears its `android_app` ptr after `onDestroy` and `AndroidApp` remains safe to access after `android_main()` returns ([#236](https://github.com/rust-mobile/android-activity/pull/236))
64+
- Support for `AndroidApp::show/hide_soft_input()` APIs in the `native-activity` backend ([#178](https://github.com/rust-mobile/android-activity/pull/178))
6465

6566
## [0.6.0] - 2024-04-26
6667

android-activity/src/game_activity/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl AndroidApp {
128128
) -> Self {
129129
// We attach to the thread before creating the AndroidApp
130130
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
131-
if let Err(err) = crate::input::jni_init(env) {
131+
if let Err(err) = crate::sdk::jni_init(env) {
132132
panic!("Failed to init JNI bindings: {err:?}");
133133
};
134134

android-activity/src/input/sdk.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,7 @@ impl AKeyCharacterMap<'_> {
107107
}
108108

109109
jni::bind_java_type! {
110-
rust_type = AInputDevice,
111-
java_type = "android.view.InputDevice",
110+
pub(crate) AInputDevice => "android.view.InputDevice",
112111
type_map {
113112
AKeyCharacterMap => "android.view.KeyCharacterMap",
114113
},
@@ -118,14 +117,6 @@ jni::bind_java_type! {
118117
}
119118
}
120119

121-
// Explicitly initialize the JNI bindings so we can get and early, upfront,
122-
// error if something is wrong.
123-
pub fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
124-
let _ = AKeyCharacterMapAPI::get(env, &Default::default())?;
125-
let _ = AInputDeviceAPI::get(env, &Default::default())?;
126-
Ok(())
127-
}
128-
129120
/// Describes the keys provided by a keyboard device and their associated labels.
130121
#[derive(Debug)]
131122
pub struct KeyCharacterMap {

android-activity/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ mod util;
395395

396396
mod jni_utils;
397397

398+
mod sdk;
399+
398400
mod waker;
399401
pub use waker::AndroidAppWaker;
400402

android-activity/src/native_activity/mod.rs

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use ndk::{asset::AssetManager, native_window::NativeWindow};
1414

1515
use crate::error::InternalResult;
1616
use crate::main_callbacks::MainCallbacks;
17+
use crate::sdk::{Activity, Context, InputMethodManager};
1718
use crate::{
1819
util, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
1920
WindowManagerFlags,
@@ -72,7 +73,7 @@ impl AndroidApp {
7273
jni_activity: &JObject,
7374
) -> Self {
7475
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
75-
if let Err(err) = crate::input::jni_init(env) {
76+
if let Err(err) = crate::sdk::jni_init(env) {
7677
panic!("Failed to init JNI bindings: {err:?}");
7778
};
7879

@@ -368,13 +369,29 @@ impl AndroidAppInner {
368369
log::error!("Can't show soft input after NativeActivity has been destroyed");
369370
return;
370371
}
371-
unsafe {
372-
let flags = if show_implicit {
373-
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
374-
} else {
375-
0
376-
};
377-
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
372+
373+
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
374+
// might be thrown by the JNI calls we make.
375+
let res = self
376+
.jvm
377+
.attach_current_thread(|env| -> jni::errors::Result<()> {
378+
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
379+
380+
let ims = Context::INPUT_METHOD_SERVICE(env)?;
381+
let im_manager = activity.as_context().get_system_service(env, ims)?;
382+
let im_manager = InputMethodManager::cast_local(env, im_manager)?;
383+
let jni_window = activity.get_window(env)?;
384+
let view = jni_window.get_decor_view(env)?;
385+
let flags = if show_implicit {
386+
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32
387+
} else {
388+
0
389+
};
390+
im_manager.show_soft_input(env, view, flags)?;
391+
Ok(())
392+
});
393+
if let Err(err) = res {
394+
log::warn!("Failed to show soft input: {err:?}");
378395
}
379396
}
380397

@@ -386,13 +403,31 @@ impl AndroidAppInner {
386403
log::error!("Can't hide soft input after NativeActivity has been destroyed");
387404
return;
388405
}
389-
unsafe {
390-
let flags = if hide_implicit_only {
391-
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
392-
} else {
393-
0
394-
};
395-
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
406+
407+
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
408+
// might be thrown by the JNI calls we make.
409+
let res = self
410+
.jvm
411+
.attach_current_thread(|env| -> jni::errors::Result<()> {
412+
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
413+
414+
let ims = Context::INPUT_METHOD_SERVICE(env)?;
415+
let imm_obj = activity.as_context().get_system_service(env, ims)?;
416+
let imm = InputMethodManager::cast_local(env, imm_obj)?;
417+
418+
let window = activity.get_window(env)?;
419+
let decor = window.get_decor_view(env)?;
420+
let token = decor.get_window_token(env)?;
421+
422+
// HIDE_IMPLICIT_ONLY == 1, HIDE_NOT_ALWAYS == 2
423+
let flags = if hide_implicit_only { 1 } else { 0 };
424+
425+
let _hidden = imm.hide_soft_input_from_window(env, token, flags)?;
426+
Ok(())
427+
});
428+
429+
if let Err(err) = res {
430+
error!("Failed to hide soft input: {err:?}");
396431
}
397432
}
398433

android-activity/src/sdk.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
jni::bind_java_type! { pub(crate) IBinder => "android.os.IBinder" }
2+
jni::bind_java_type! {
3+
pub(crate) View => "android.view.View",
4+
type_map {
5+
IBinder => "android.os.IBinder",
6+
},
7+
methods {
8+
fn get_window_token() -> IBinder,
9+
}
10+
}
11+
jni::bind_java_type! {
12+
pub(crate) InputMethodManager => "android.view.inputmethod.InputMethodManager",
13+
type_map {
14+
View => "android.view.View",
15+
IBinder => "android.os.IBinder",
16+
},
17+
methods {
18+
fn show_soft_input(view: View, flags: i32) -> bool,
19+
fn hide_soft_input_from_window(window_token: IBinder, flags: i32) -> bool,
20+
}
21+
}
22+
jni::bind_java_type! {
23+
pub(crate) Context => "android.content.Context",
24+
fields {
25+
#[allow(non_snake_case)]
26+
static INPUT_METHOD_SERVICE: JString
27+
},
28+
methods {
29+
fn get_system_service(service_name: JString) -> JObject,
30+
}
31+
}
32+
jni::bind_java_type! {
33+
pub(crate) Window => "android.view.Window",
34+
type_map {
35+
View => "android.view.View",
36+
},
37+
methods {
38+
fn get_decor_view() -> View,
39+
}
40+
}
41+
jni::bind_java_type! {
42+
pub(crate) Activity => "android.app.Activity",
43+
type_map {
44+
Context => "android.content.Context",
45+
Window => "android.view.Window",
46+
},
47+
is_instance_of {
48+
context: Context
49+
},
50+
methods {
51+
fn get_window() -> Window,
52+
}
53+
}
54+
55+
// Explicitly initialize the JNI bindings so we can get and early, upfront,
56+
// error if something is wrong.
57+
pub(crate) fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
58+
let _ = IBinderAPI::get(env, &Default::default())?;
59+
let _ = ViewAPI::get(env, &Default::default())?;
60+
let _ = InputMethodManagerAPI::get(env, &Default::default())?;
61+
let _ = ContextAPI::get(env, &Default::default())?;
62+
let _ = WindowAPI::get(env, &Default::default())?;
63+
let _ = ActivityAPI::get(env, &Default::default())?;
64+
let _ = crate::input::AKeyCharacterMapAPI::get(env, &Default::default())?;
65+
let _ = crate::input::AInputDeviceAPI::get(env, &Default::default())?;
66+
Ok(())
67+
}

0 commit comments

Comments
 (0)