Ratchet 98 → 96. Both build an Intent and call startActivity, which is why they're one module — they share every JNI call and differ only in which extras go on the intent.
Neither reports whether anything happened
startActivity throws ActivityNotFoundException when nothing on the device handles the intent, and that is the only failure either action can see. Once another app is launched, what the user does there is unobservable from here — share in particular returns before the chooser is even drawn.
So true means "the intent was accepted", never "the user shared something". A page treating it as the latter is wrong on both platforms. Kept as-is rather than improved: the shim's answer is the contract while the shim exists.
share has exactly one branch, and it isn't cosmetic
An empty EXTRA_SUBJECT produces a mail draft whose subject line is blank rather than absent, and some clients then refuse to send it. So skipping the extra and setting it to "" are different outcomes, not a tidy-up. The Kotlin guards it; this guards it; a mutation that always sets it fails its own test.
The fake JVM generalised
Same shape as the clipboard module: GetMethodID hands back the method name as the jmethodID, so the fake records the call sequence rather than the test guessing at it.
That's what makes a claim like "the action is read off Intent rather than spelled as a literal" actually assertable — the test checks ACTION_VIEW was looked up as a static field, not that some string turned up somewhere.
Uri.parse doesn't validate and doesn't throw; it accepts anything and yields a null scheme for nonsense. So the failing case is exercised by making startActivity leave a pending exception — which is what a device with no browser actually does.
One small judgment
openURL declining logs at debug, not warn. Answering false is the documented outcome when nothing handles the scheme, and the Kotlin catches exactly that — a warn line would fill logcat for a page doing something perfectly reasonable.
Verified
| mutation | result |
|---|---|
always set EXTRA_SUBJECT | the empty-title test fails |
| ratchet lowered 96 → 95 | MigrationWentBackwards — the count really is 96 |
zig build test:android → 72 tests, 11/11 steps · both .so build · generator 10/10.
Where Android stands
96 of 103 unmigrated, from 103 at the start: getDeviceInfo, getMemoryUsage, log, clipboardRead, clipboardWrite, openURL, share.
Next candidates are less clean, and worth naming: setKeepAwake and lockOrientation both need runOnUiThread, which needs a Runnable — and JNI can only produce one by registering natives on a class that must already exist in the APK. toggleFlashlight and getAppState read Kotlin-held state. Those are structural walls of the kind the iOS deferral table records, not a backlog.