Skip to content

Notification Button Not Displayed at Top of App

Metadata

  • Date: 2026-04-26
  • Status: fixed
  • Severity: high
  • Related issue/ticket: N/A
  • Owner: N/A

About

Overview: - The recording-controls notification does not appear as a high-importance banner at the top of the screen on either iOS or Android. - On iOS the setNotificationHandler callback explicitly sets shouldShowBanner: false, silencing the foreground presentation entirely. - On Android the NotificationChannel is created with IMPORTANCE_LOW and the NotificationCompat.Builder sets PRIORITY_LOW + FOREGROUND_SERVICE_DEFERRED, both of which prevent the heads-up (peek) presentation that slides in at the top. - This matters because the recording-controls notification is intended to be a high-importance, persistent control surface — users must be able to see and interact with it while the app is in the foreground or background.

Technical Questions: - Are we making assumptions? No — the shouldShowBanner flag and Android importance values are explicit API choices that are clearly wrong. - How old is this bug? Introduced with the initial notification implementation. - Anything obvious missed? Yes — shouldShowBanner: false is the single line that kills iOS banner display; IMPORTANCE_LOW is the single constant that kills Android heads-up. - Specific states to reproduce: Foreground app on iOS; any state on Android (channel importance affects even background notifications).

Resources: - /main/app/lib/recording-control-orchestrator.tsinitializeIOSNotifications, lines 152-159 - /main/app/wearables-module/android/src/main/java/expo/modules/wearablesmodule/RecordingControlService.ktensureNotificationChannel (line 48-58) and buildNotification (line 90, 94)

Steps to cause failure

flowchart LR
    AppStart --> InitOrchestrator --> setNotificationHandler
    setNotificationHandler -->|shouldShowBanner:false| NotificationSilenced
    InitOrchestrator --> ensureNotificationChannel
    ensureNotificationChannel -->|IMPORTANCE_LOW| NoHeadsUp

System

flowchart TD
    TS[recording-control-orchestrator.ts] -->|iOS: expo-notifications| ExpoNotif[expo-notifications setNotificationHandler]
    TS -->|Android: native bridge| KT[RecordingControlService.kt]
    KT --> Channel[NotificationChannel IMPORTANCE_LOW]
    KT --> Builder[NotificationCompat.Builder PRIORITY_LOW]
    ExpoNotif -->|shouldShowBanner:false| Suppressed[Banner suppressed in foreground]
    Channel --> NoHeadsUp[No heads-up on Android]
    Builder --> Deferred[FOREGROUND_SERVICE_DEFERRED delays display]

Reproduction Details

  1. Launch the Encache app on iOS or Android.
  2. Observe that the recording-controls notification does not appear as a banner/heads-up at the top of the screen.
  3. On iOS: the notification is silently added to Notification Center only (no banner).
  4. On Android: the notification channel is IMPORTANCE_LOW; the system never shows a heads-up pop.

Reproduction test (unit preferred): main/app/__tests__/recording-control-orchestrator.test.ts — added test "setNotificationHandler uses shouldShowBanner:true" and "scheduleNotificationAsync is called with high priority sound config" to verify iOS banner configuration; Android channel importance is native-side only, verified by code inspection.

Notes for PR

Root cause is two independent mis-configurations in separate layers:

iOS (recording-control-orchestrator.ts) setNotificationHandler returned shouldShowBanner: false. The correct value is true. The shouldShowList field should also be true to retain Notification Center presence. No other code change is required on the iOS path.

Android (RecordingControlService.kt) NotificationChannel was created with NotificationManager.IMPORTANCE_LOW. Changed to IMPORTANCE_HIGH. NotificationCompat.Builder set setPriority(PRIORITY_LOW). Changed to PRIORITY_HIGH. setForegroundServiceBehavior(FOREGROUND_SERVICE_DEFERRED) explicitly tells the system to delay showing the foreground-service notification. Changed to FOREGROUND_SERVICE_IMMEDIATE so the notification appears at the top as soon as the foreground service starts.

The channel ID was also bumped from encache_recording_controls_v5 to encache_recording_controls_v6 because Android caches channel importance at creation time — existing installs with the old v5 channel would ignore the importance upgrade without a new channel ID.

Audit Log

ID Action Note Context
1 Create audit log Initialize bug investigation Notification banner absent on both platforms
2 Read iOS handler Found shouldShowBanner: false in setNotificationHandler recording-control-orchestrator.ts line 155
3 Read Android service Found IMPORTANCE_LOW, PRIORITY_LOW, FOREGROUND_SERVICE_DEFERRED RecordingControlService.kt lines 50, 90, 94
4 Write failing repro test Added test asserting shouldShowBanner: true in handler config Test fails before fix
5 Apply iOS fix shouldShowBanner: falsetrue recording-control-orchestrator.ts
6 Apply Android fix IMPORTANCE_LOWHIGH, PRIORITY_LOWHIGH, DEFERREDIMMEDIATE, bump channel ID v5→v6 RecordingControlService.kt
7 Confirm tests pass All existing + new tests green npm test
8 Revert fix + confirm failure Test fails again without fix Causality confirmed
9 Re-apply fix Tests pass Fix is authoritative

Verification

  • [x] Reproduced failure before fix
  • [x] Reproduction test fails before fix
  • [x] Root cause identified with evidence
  • [x] Fix applied at source (no workaround-only patch)
  • [x] Reproduction test passes after fix
  • [x] Reproduction path now passes
  • [x] Regression test added/updated
  • [x] Verified no duplicate solved-bug log exists for same root cause