Skip to main content

max / goingson

Give the Android app its own icon, and a TLS failure it can report The launcher art was Tauri's default: `android init` writes its own ic_launcher set and never looks at src-tauri/icons/. Regenerated from the GoingsOn source art, which also brings an adaptive icon so Android 8+ shapes and themes it like every other launcher entry. `cargo tauri icon` rewrites the iOS appiconset and the desktop set as well, and both are reverted here. The iOS one matters: it comes back out with an alpha channel, which is the ITMS-90717 rejection release-ios.sh already flattens for. An Android task has no business touching it. The failure path added with the verifier init could not have reported anything. MainActivity calls it before super.onCreate(), which is the point, but tracing_subscriber::fmt::init() runs inside that same super call from the mobile entry point -- so tracing::error! at the failure site wrote to a subscriber that did not exist yet and was dropped. The one diagnostic for "no TLS at all" was invisible. The error is parked in a OnceLock and logged from the entry point once there is somewhere for it to go, still well ahead of the first network call. Verified on an SM-F966U1: installs, launches, no panic and no init failure.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-17 03:00 UTC
Signed with PGP, not checked
Commit: a4362aa876dc613593861760c020141110f62915
Parent: 568361b
19 files changed, +48 insertions, -8 deletions
@@ -20,20 +20,34 @@
20 20 //! initialised, so this module follows rustls-platform-verifier's jni version
21 21 //! and nothing else.
22 22
23 + use std::sync::OnceLock;
24 +
23 25 use jni::EnvUnowned;
24 26 use jni::objects::JObject;
25 27
28 + /// Set when initialisation fails, drained by [`report_init_failure`].
29 + ///
30 + /// This exists because of an ordering trap. The native method below runs from
31 + /// `MainActivity.onCreate` *before* `super.onCreate()`, which is what makes it
32 + /// early enough to be useful -- but `tracing_subscriber::fmt::init()` runs
33 + /// inside that same super call, from the mobile entry point in `lib.rs`. Logging
34 + /// the failure where it happens would therefore write to a subscriber that does
35 + /// not exist yet and be dropped, leaving the one diagnostic for "no TLS at all"
36 + /// invisible. So the error is parked here and logged once there is somewhere for
37 + /// it to go.
38 + static INIT_FAILURE: OnceLock<String> = OnceLock::new();
39 +
26 40 /// `MainActivity.initRustlsPlatformVerifier(Context)`.
27 41 ///
28 42 /// Takes the *application* context rather than the activity: the crate holds a
29 43 /// global reference to whatever it is given for the life of the process, and
30 44 /// pinning an Activity there would leak it across every rotation and fold.
31 45 ///
32 - /// A failure is logged rather than thrown. GoingsOn is local-first and a
46 + /// A failure is recorded rather than thrown. GoingsOn is local-first and a
33 47 /// tasks-and-calendar session is entirely usable with no network, so taking the
34 48 /// whole app down at startup would cost more than it explains. The tradeoff is
35 - /// that a failure here surfaces later as a panic on first network use, which is
36 - /// why the error is logged loudly at the point it actually happens.
49 + /// that a failure surfaces later as a panic on first network use, which is why
50 + /// [`report_init_failure`] exists to name the real cause before that happens.
37 51 #[unsafe(no_mangle)]
38 52 pub extern "system" fn Java_com_goingson_app_MainActivity_initRustlsPlatformVerifier<'local>(
39 53 mut env: EnvUnowned<'local>,
@@ -42,13 +56,24 @@
42 56 ) {
43 57 env.with_env(|env| {
44 58 if let Err(e) = rustls_platform_verifier::android::init_with_env(env, context) {
45 - tracing::error!(
46 - error = %e,
47 - "rustls-platform-verifier failed to initialise; every TLS connection \
48 - will fail until the app is restarted"
49 - );
59 + let _ = INIT_FAILURE.set(e.to_string());
50 60 }
51 61 Ok::<(), jni::errors::Error>(())
52 62 })
53 63 .resolve::<jni::errors::LogErrorAndDefault>();
54 64 }
65 +
66 + /// Logs an initialisation failure, if there was one.
67 + ///
68 + /// Call once from the mobile entry point, after the tracing subscriber is
69 + /// installed and before anything can reach the network.
70 + pub fn report_init_failure() {
71 + if let Some(e) = INIT_FAILURE.get() {
72 + tracing::error!(
73 + error = %e,
74 + "rustls-platform-verifier did not initialise: no TLS connection can be \
75 + verified, so sync and mail will panic rather than fall back. Restarting \
76 + the app is the only recovery."
77 + );
78 + }
79 + }
@@ -437,6 +437,12 @@
437 437
438 438 tracing_subscriber::fmt::init();
439 439
440 + // Android hands the JVM to rustls-platform-verifier from MainActivity, which
441 + // runs before this function and so before there is a subscriber to log to.
442 + // Now there is one, and this is still ahead of the first network call.
443 + #[cfg(target_os = "android")]
444 + crate::android_tls::report_init_failure();
445 +
440 446 build_mobile_app()
441 447 .run(tauri::generate_context!())
442 448 .expect("error while running tauri application");
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file