1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//! Closing the activity from inside the app: the Android half of
//! [`cranpose_services::request_exit`].
//!
//! It is a JNI call to `Activity.finish()`, and it has to be.
//! `ANativeActivity_finish` looks like the obvious answer and is a trap:
//! `AndroidApp::activity_as_ptr` returns a **jobject** global reference to the
//! Activity, not an `ANativeActivity*` — android-activity's own source says so,
//! and `android_jni` in this crate has always treated it as a jobject. Passing
//! it to the NDK function type-confuses the two. Measured on a Pixel Watch 3
//! before this was understood: `app died, no saved state` and `exited due to
//! signal 9 (Killed)`, followed by a second of black while the system cleaned
//! up. That reads exactly like a working-but-slow exit, which is what makes it
//! worth a module of its own with the reason written down.
//!
//! `finish()` does not end the process or stop this frame. The activity is
//! destroyed asynchronously and arrives back at the run loop as
//! `MainEvent::Destroy`, which is the same path the system's own back takes, so
//! an app-requested exit and a user-requested one leave through the same door.
use crate::android_jni::{clear_pending_android_jni_exception, with_android_activity_env};
use jni::{jni_sig, jni_str};
/// Asks the platform to finish the activity. Returns whether the call landed.
///
/// The caller needs the answer: the exit flag is the app's only record that it
/// asked to close, and consuming it for a call that did not happen loses the
/// request with nothing left to retry from.
pub(crate) fn finish_activity(app: &android_activity::AndroidApp) -> bool {
let finished = with_android_activity_env(app, |env, activity| {
env.call_method(&activity, jni_str!("finish"), jni_sig!("()V"), &[])
.map(|_| ())
.map_err(|error| {
// A failed JNI call can leave an exception pending on the
// thread, and the NEXT JNI call on that thread is the one that
// dies for it -- with this call's exception, in a stack that
// has nothing to do with finishing an activity. Every other
// caller in this crate clears before returning the error, and
// `clear_pending_android_jni_exception` logs it on the way out
// so the cause is not simply swallowed.
clear_pending_android_jni_exception(env);
format!("Activity.finish() failed: {error}")
})
});
match finished {
Ok(()) => true,
Err(error) => {
log::error!("could not finish the activity: {error}");
false
}
}
}