mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-09 16:27:28 +00:00
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>
This commit is contained in:
committed by
Robert Bragg
parent
c1d00b9191
commit
c3ed6ba77d
@@ -61,6 +61,7 @@ fn android_on_create(state: &OnCreateState) {
|
||||
- *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))
|
||||
- *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))
|
||||
- *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))
|
||||
- Support for `AndroidApp::show/hide_soft_input()` APIs in the `native-activity` backend ([#178](https://github.com/rust-mobile/android-activity/pull/178))
|
||||
|
||||
## [0.6.0] - 2024-04-26
|
||||
|
||||
|
||||
@@ -128,7 +128,7 @@ impl AndroidApp {
|
||||
) -> Self {
|
||||
// We attach to the thread before creating the AndroidApp
|
||||
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
if let Err(err) = crate::input::jni_init(env) {
|
||||
if let Err(err) = crate::sdk::jni_init(env) {
|
||||
panic!("Failed to init JNI bindings: {err:?}");
|
||||
};
|
||||
|
||||
|
||||
@@ -107,8 +107,7 @@ impl AKeyCharacterMap<'_> {
|
||||
}
|
||||
|
||||
jni::bind_java_type! {
|
||||
rust_type = AInputDevice,
|
||||
java_type = "android.view.InputDevice",
|
||||
pub(crate) AInputDevice => "android.view.InputDevice",
|
||||
type_map {
|
||||
AKeyCharacterMap => "android.view.KeyCharacterMap",
|
||||
},
|
||||
@@ -118,14 +117,6 @@ jni::bind_java_type! {
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly initialize the JNI bindings so we can get and early, upfront,
|
||||
// error if something is wrong.
|
||||
pub fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
|
||||
let _ = AKeyCharacterMapAPI::get(env, &Default::default())?;
|
||||
let _ = AInputDeviceAPI::get(env, &Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Describes the keys provided by a keyboard device and their associated labels.
|
||||
#[derive(Debug)]
|
||||
pub struct KeyCharacterMap {
|
||||
|
||||
@@ -395,6 +395,8 @@ mod util;
|
||||
|
||||
mod jni_utils;
|
||||
|
||||
mod sdk;
|
||||
|
||||
mod waker;
|
||||
pub use waker::AndroidAppWaker;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ use ndk::{asset::AssetManager, native_window::NativeWindow};
|
||||
|
||||
use crate::error::InternalResult;
|
||||
use crate::main_callbacks::MainCallbacks;
|
||||
use crate::sdk::{Activity, Context, InputMethodManager};
|
||||
use crate::{
|
||||
util, AndroidApp, AndroidAppWaker, ConfigurationRef, InputStatus, MainEvent, PollEvent, Rect,
|
||||
WindowManagerFlags,
|
||||
@@ -72,7 +73,7 @@ impl AndroidApp {
|
||||
jni_activity: &JObject,
|
||||
) -> Self {
|
||||
jvm.with_local_frame(10, |env| -> jni::errors::Result<_> {
|
||||
if let Err(err) = crate::input::jni_init(env) {
|
||||
if let Err(err) = crate::sdk::jni_init(env) {
|
||||
panic!("Failed to init JNI bindings: {err:?}");
|
||||
};
|
||||
|
||||
@@ -368,13 +369,29 @@ impl AndroidAppInner {
|
||||
log::error!("Can't show soft input after NativeActivity has been destroyed");
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let flags = if show_implicit {
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_showSoftInput(na as *mut _, flags);
|
||||
|
||||
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
|
||||
// might be thrown by the JNI calls we make.
|
||||
let res = self
|
||||
.jvm
|
||||
.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
|
||||
|
||||
let ims = Context::INPUT_METHOD_SERVICE(env)?;
|
||||
let im_manager = activity.as_context().get_system_service(env, ims)?;
|
||||
let im_manager = InputMethodManager::cast_local(env, im_manager)?;
|
||||
let jni_window = activity.get_window(env)?;
|
||||
let view = jni_window.get_decor_view(env)?;
|
||||
let flags = if show_implicit {
|
||||
ndk_sys::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT as i32
|
||||
} else {
|
||||
0
|
||||
};
|
||||
im_manager.show_soft_input(env, view, flags)?;
|
||||
Ok(())
|
||||
});
|
||||
if let Err(err) = res {
|
||||
log::warn!("Failed to show soft input: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,13 +403,31 @@ impl AndroidAppInner {
|
||||
log::error!("Can't hide soft input after NativeActivity has been destroyed");
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let flags = if hide_implicit_only {
|
||||
ndk_sys::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ndk_sys::ANativeActivity_hideSoftInput(na as *mut _, flags);
|
||||
|
||||
// Note: `.attach_current_thread()` will also handle catching any Java exceptions that
|
||||
// might be thrown by the JNI calls we make.
|
||||
let res = self
|
||||
.jvm
|
||||
.attach_current_thread(|env| -> jni::errors::Result<()> {
|
||||
let activity = env.as_cast::<Activity>(self.activity.as_ref())?;
|
||||
|
||||
let ims = Context::INPUT_METHOD_SERVICE(env)?;
|
||||
let imm_obj = activity.as_context().get_system_service(env, ims)?;
|
||||
let imm = InputMethodManager::cast_local(env, imm_obj)?;
|
||||
|
||||
let window = activity.get_window(env)?;
|
||||
let decor = window.get_decor_view(env)?;
|
||||
let token = decor.get_window_token(env)?;
|
||||
|
||||
// HIDE_IMPLICIT_ONLY == 1, HIDE_NOT_ALWAYS == 2
|
||||
let flags = if hide_implicit_only { 1 } else { 0 };
|
||||
|
||||
let _hidden = imm.hide_soft_input_from_window(env, token, flags)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
if let Err(err) = res {
|
||||
error!("Failed to hide soft input: {err:?}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
jni::bind_java_type! { pub(crate) IBinder => "android.os.IBinder" }
|
||||
jni::bind_java_type! {
|
||||
pub(crate) View => "android.view.View",
|
||||
type_map {
|
||||
IBinder => "android.os.IBinder",
|
||||
},
|
||||
methods {
|
||||
fn get_window_token() -> IBinder,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) InputMethodManager => "android.view.inputmethod.InputMethodManager",
|
||||
type_map {
|
||||
View => "android.view.View",
|
||||
IBinder => "android.os.IBinder",
|
||||
},
|
||||
methods {
|
||||
fn show_soft_input(view: View, flags: i32) -> bool,
|
||||
fn hide_soft_input_from_window(window_token: IBinder, flags: i32) -> bool,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Context => "android.content.Context",
|
||||
fields {
|
||||
#[allow(non_snake_case)]
|
||||
static INPUT_METHOD_SERVICE: JString
|
||||
},
|
||||
methods {
|
||||
fn get_system_service(service_name: JString) -> JObject,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Window => "android.view.Window",
|
||||
type_map {
|
||||
View => "android.view.View",
|
||||
},
|
||||
methods {
|
||||
fn get_decor_view() -> View,
|
||||
}
|
||||
}
|
||||
jni::bind_java_type! {
|
||||
pub(crate) Activity => "android.app.Activity",
|
||||
type_map {
|
||||
Context => "android.content.Context",
|
||||
Window => "android.view.Window",
|
||||
},
|
||||
is_instance_of {
|
||||
context: Context
|
||||
},
|
||||
methods {
|
||||
fn get_window() -> Window,
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly initialize the JNI bindings so we can get and early, upfront,
|
||||
// error if something is wrong.
|
||||
pub(crate) fn jni_init(env: &jni::Env) -> jni::errors::Result<()> {
|
||||
let _ = IBinderAPI::get(env, &Default::default())?;
|
||||
let _ = ViewAPI::get(env, &Default::default())?;
|
||||
let _ = InputMethodManagerAPI::get(env, &Default::default())?;
|
||||
let _ = ContextAPI::get(env, &Default::default())?;
|
||||
let _ = WindowAPI::get(env, &Default::default())?;
|
||||
let _ = ActivityAPI::get(env, &Default::default())?;
|
||||
let _ = crate::input::AKeyCharacterMapAPI::get(env, &Default::default())?;
|
||||
let _ = crate::input::AInputDeviceAPI::get(env, &Default::default())?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user