Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions android-activity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion android-activity/src/game_activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:?}");
};

Expand Down
11 changes: 1 addition & 10 deletions android-activity/src/input/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
Expand All @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions android-activity/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ mod util;

mod jni_utils;

mod sdk;

mod waker;
pub use waker::AndroidAppWaker;

Expand Down
65 changes: 50 additions & 15 deletions android-activity/src/native_activity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:?}");
};

Expand Down Expand Up @@ -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:?}");
}
}

Expand All @@ -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:?}");
}
}

Expand Down
67 changes: 67 additions & 0 deletions android-activity/src/sdk.rs
Original file line number Diff line number Diff line change
@@ -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(())
}