From bf5087a598d015ded6d6fa52c965caa9d5facf59 Mon Sep 17 00:00:00 2001 From: iris <2+iris@noreply.localhost> Date: Sat, 5 Sep 2026 14:08:40 -0400 Subject: [PATCH] iris/android: fix background-thread redraw crash, add missing INTERNET permission Two real bugs found bringing up I5's Android transcript client, neither specific to that screen -- any future caller of Tasks::redraw_handle() from a background thread would hit the first one. AndroidRedrawHandle::request_redraw called View::post_frame_callback from a tokio worker thread; its Java side calls Choreographer.getInstance(), which throws IllegalStateException unless the *calling* thread already has a Looper, and a JNI-attached background thread has none. That crashed the whole process (SIGABRT, unwrap() on a JavaException) the first time a background fetch asked for a second frame. Fixed by routing through View::post_delayed(0) instead, Android's own thread-safe way to queue work onto a View's UI thread, landing on a new IrisViewPeer::delayed_callback override that drains tasks and renders -- same body as do_frame, now running safely on the UI thread. iris-android-app's manifest never needed INTERNET before (the tabs demo makes no network call); its absence read as EPERM ("Operation not permitted") from UreqTransport::new's connect, not the ECONNREFUSED/ENETUNREACH a dead server would give. Full account in RUST.md's I5 box and IRIS.md's Tasks::redraw_handle entry. Co-Authored-By: Claude Fable 5.1 --- .../app/src/main/AndroidManifest.xml | 9 ++++++++ iris/src/android/render.rs | 21 +++++++++++++++---- iris/src/android/view.rs | 14 +++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/iris/android-app/app/src/main/AndroidManifest.xml b/iris/android-app/app/src/main/AndroidManifest.xml index 5abb34b..5389aa7 100644 --- a/iris/android-app/app/src/main/AndroidManifest.xml +++ b/iris/android-app/app/src/main/AndroidManifest.xml @@ -1,6 +1,15 @@ + + + ViewPeer for IrisViewPeer { self.render(ctx); } + /// Where `AndroidRedrawHandle::request_redraw` (`android/render.rs`) + /// actually lands: `View.postDelayed`'s Runnable resolves to this, on + /// the UI thread, which is what makes it safe to call from a background + /// task's own thread when `post_frame_callback`'s `Choreographer` + /// requirement (a `Looper` on the *calling* thread) is not. Same body + /// as `do_frame` -- draining tasks and rendering immediately is a + /// perfectly good answer to "a background fetch has new state," and + /// avoids a second frame-scheduling path to keep in sync with the real + /// one. + fn delayed_callback(&mut self, ctx: &mut CallbackCtx) { + self.drain_tasks(); + self.render(ctx); + } + fn as_input_connection(&mut self) -> Option<&mut dyn InputConnection> { Some(self) }