E3: the Kotlin/Java shell over a JNI bridge into Rust (RUST.md)

Two Java classes (MainActivity, NotificationService) hand their lifecycle
to a new android-shell crate built on client-core; client-core gains
notifications.rs (the /notifications SSE parse and attention_line, ported
from Notifications.kt). Packaged as a new app/shellApp Gradle module
rather than a rewrite of app/androidApp in place, so that module's working
Compose UI is untouched.

Both pass conditions held on the emulator: a notification arrived in
Android's drawer with the app closed, and a shared text share landed as a
real message in a sandbox session's transcript. Found and fixed three
real bugs along the way (a silently-wrong JNI signature from a generic
JObject parameter, a class-by-name lookup failing on this crate's own
background thread for lack of an app ClassLoader, and onStartCommand
opening two /notifications connections per enrollment -- the last a
latent bug in Notifications.kt itself). Full account, exact commands and
what was deliberately cut are in RUST.md's E3 box.

Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
This commit is contained in:
irisandClaude Sonnet committed 2026-09-05 06:47:32 -04:00
1 parent 8adda94a7a
commit c9b273ff16
16 files changed
+2996 -6

No files matched your search

+62
View File
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Mirrors androidApp's manifest (AGENTS.md: reuse it rather than
re-deriving it) for the permissions and declarations E3 actually
exercises. Not carried over: the QR scanner activity (this
experiment enrolls via the aiappshell://enroll deep link directly,
per AGENTS.md's ui-sandbox.sh banner) and the app icon warning
suppression below, for the same reason androidApp's is there. -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<application
android:label="AI Sessions (shell)"
android:allowBackup="true"
android:theme="@android:style/Theme.Material.Light.NoActionBar"
tools:ignore="MissingApplicationIcon">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Enrollment: aiappshell://enroll?host=...&port=...&token=...,
per AGENTS.md's ui-sandbox.sh banner (fed to this app with
`adb shell am start -a android.intent.action.VIEW -d
'aiappshell://enroll?...'`, or -n'd at this component
directly if a second app also claims the aiapp scheme). -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="aiappshell" android:host="enroll" />
</intent-filter>
<!-- The share sheet - see android-shell's share.rs. -->
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<!-- specialUse, not dataSync, for the reason androidApp's manifest
gives: a connection that has to keep listening overnight
cannot accept dataSync's six-hour cap. -->
<service
android:name=".NotificationService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="E3 experiment: holds one connection to the sandbox server so a
session that needs an answer can be reported while the app is closed." />
</service>
</application>
</manifest>