The manifest said ".MessengerService" while the activity beside it says
".app.MainActivity" — so the class resolved to
io.flamenet.messenger.MessengerService, which does not exist. `dumpsys
package` reported "No services found" and starting it would have thrown
ClassNotFoundException at the moment the app was backgrounded, which is
the moment nobody is watching.
Verified on the emulator after the fix, end to end:
- backgrounding the app starts it, isForeground=true, types=0x200
(REMOTE_MESSAGING), ongoing notice on flamenet.ongoing
- iOS sent PUSH2 at 932.474; the Android notification posted at 931.466
on a device whose clock matches the host — so the alert arrived about a
second BEFORE the sender finished rendering its own copy
- repeated with a fresh marker to prove it was not a stale notification:
when= moved 848.867 -> 931.466
That is a closed app on Android alerted in about a second with nobody in
the middle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two platforms, two mechanisms, because iOS leaves no choice.
Android gets MessengerService, a foreground service holding the long-poll
with nobody in the middle. It rebuilds its session from the vault rather
than sharing the ViewModel's — by the time it runs the Activity may be
gone, which is the situation it exists for. Notifications for messages,
and a high-priority one naming the caller for an incoming call, because an
unnamed ringing phone is not answerable.
⚠️ The persistent notification is not a bug. Android requires a visible
notice for a foreground service; removing it means the OS kills the
connection. That is the price of not routing wake-ups through Google.
⚠️ Exactly one poller at a time: the ViewModel polls while the app is on
screen, the service while it is not, handed over in onStart/onStop. Two
would race for the same envelopes and double-notify, so the pair has to
stay symmetrical.
iOS gets APNs, and that IS Apple on the delivery path — there is no
alternative, because iOS suspends a backgrounded process and provides
exactly one way to wake it. What Apple learns is metadata: that a device
was woken and when. The push is content-free by construction, since the
relay holds ciphertext it cannot read. ⚠️ Nothing may later put the
message into the push payload; anything that could would mean the relay
had read it.
Relay stores one token per DEVICE, not per user — messages are sealed per
device, so waking the wrong one wakes an app with nothing to decrypt.
Re-registering replaces rather than accumulates, because APNs reissues
tokens and stale ones wake devices that no longer exist. An empty token
means forget: a sign-out must reach this, or a handset goes on being woken
for an account it no longer holds. Android tokens are refused outright —
it receives directly and needs no row.
Not yet done: the APNs sender itself, which needs a .p8 auth key that only
a human with the developer account can mint.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tapping New opened the form, the name field took text, and then nothing:
no member checkboxes, no Create group button, Cancel the only thing that
worked. The controls were there all along, below the fold on a screen that
could not scroll.
The scroll exemption was deliberate and its reason had expired. It read
"only the screens WITHOUT a lazy list may scroll — Contacts owns a
LazyColumn, and a lazy list inside a vertically scrolling Column is
measured with infinite height and crashes outright." True of an UNBOUNDED
list; the buddy list has carried heightIn(max = 320.dp) since it was moved
into its own card. Groups sits below it, so everything the form added fell
off the bottom.
Measured on a 1080x2400 emulator: the name field at y~1879, Create group
past the tab bar at y~2280, and a uiautomator dump finding no scrollable
container and no Create group node at all.
Checked every lazy list on the screen before changing it — the buddy list
is bounded, Stories is a LazyRow. Driven on the emulator afterwards: the
checkbox and button are reachable, a group was created, and it arrived on
iOS as "Demo Group, 2 members".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>