Linear: https://linear.app/engineereddev/issue/ENG-263
What's in this PR (22 files, all under apps/android/):
notify/PushNotifierimplementsPushSink: channelpushes(IMPORTANCE_HIGH), eligibility (source ≠ self AND target null or self), just-in-time decryption, §14.6 layouts (note BigText + Copy; link title-or-host + Open + Copy; file name/size + Download →DownloadRequested, "Coming soon" for now), brand icon/accent,setWhen= createdAt, stable id + tag = push id, group summary at 3+.CopyReceiver(non-exported, immutable broadcast PendingIntent): plain-text clip, toast only below API 33, cancels the notification. Open goes through a non-exportedOpenActivitythat cancels the notification, thenACTION_VIEWfor http(s) only.- Body tap →
MainActivitywithpushId, passed throughPneumoNavHost(Home consumes it in ENG-264).push_deletedcancels the notification (SyncEnginehook). - POST_NOTIFICATIONS denied: no crash; Settings shows "Notifications are off" with a button to system settings (new preview + 2 Roborazzi baselines).
- Tests:
PushNotifierTest,NotificationActionsTest(Robolectric, ShadowNotificationManager, SDK 32 vs 34 toast),SyncEngineTestdelete cancel,ShellRestorationTest.
Verify (lead, on eca40d7):
mise run check:android→ exit 0 (Spotless, detekt, Lint, Roborazzi verify)./gradlew testDebugUnitTest --rerun→ exit 0 (165 tests, 0 failures)./gradlew pixel6Api34DebugAndroidTest --rerun→ exit 0 (11 tests, 0 failures)- Acceptance (implementer, API 34 emulator, local random-port server):
dev-client send note "hello"→ notification → Copy → paste showshello(screenshot checked by the lead, not committed). Open, three-push grouping and permission recovery also exercised.
Deviation: Open uses a non-exported OpenActivity dispatcher because action-button taps don't honour auto-cancel.
🤖 Generated with Claude Code