# Notifications Background polling that surfaces new bank activity as system notifications, plus an in-app bottom sheet for reading them. Opt-in only — the polling service is started from [Settings → Notifications](27-settings-notifications.md). --- ## Foreground Service — `NotificationPollingService` File: `service/NotificationPollingService.kt`. Runs as a foreground service while the user has notifications enabled. Polls every **30 seconds** (`POLL_INTERVAL_MS = 30_000L`) on a `SupervisorJob` IO scope. ### Lifecycle 1. `onCreate`: creates the service channel (low importance, no badge), calls `startForeground(SERVICE_NOTIF_ID, ...)` with a persistent low-priority notification, then starts the polling loop 2. `onStartCommand`: returns `START_STICKY` so Android relaunches the service after kills 3. `onDestroy`: cancels the coroutine scope ### Poll Cycle Each tick calls `pollBml()` and `pollMib()` sequentially. Both share the same shape: | Step | Description | |---|---| | 1 | For every active session in `app.bmlSessions` / `app.mibSessions`, fetch the latest activity page | | 2 | Compare with `NotificationsCache.loadBml(loginId)` / `loadMib(loginId, readIds)` | | 3 | If the cache is empty (first run), persist the page silently — no system notifications are emitted on first sync | | 4 | Otherwise, find IDs not in the cache, persist the merged list, and post a system notification per new item | MIB activity is fetched via `MibActivityHistoryClient.fetchActivity(session, loginId, 1, 100)`. BML uses `BmlNotificationsClient.fetchNotifications(session, loginId, page = 1)`. Errors are caught per-loginId so one bad session does not block the rest. ### Channels `ensureLoginChannel(bank, loginId)` lazily creates a `NotificationChannel` named `bank_{bank}_{loginId}` with `IMPORTANCE_DEFAULT`. The channel display name uses the profile's `name` from `bmlProfilesMap` / `mibProfilesMap` ("BML · Personal Name" etc.), falling back to the bare login id. This gives the user per-bank-per-profile channel control in system settings. System notifications use `R.drawable.ic_bell` and a content intent that re-launches the app via `getLaunchIntentForPackage(packageName)`. --- ## In-App Sheet — `NotificationsSheetFragment` Opened from the bell icon in the toolbar (`HomeActivity.openNotificationsSheet()` at line 687). A `BottomSheetDialogFragment` containing: - A `TabLayout` + `ViewPager2` with one page per active bank login - Each page is a `RecyclerView` of `AppNotification` grouped by date headers - An "Mark all read" action Notifications are loaded from `NotificationsCache` first for instant display, then refreshed from the network. Read state is tracked in `NotificationsCache` (`getMibReadIds()` etc.) so the bell toolbar icon (`HomeActivity.kt:640-684`) can switch between `ic_bell` and `ic_bell_read`. `onUnreadCountChanged` callback is wired from `HomeActivity` so the bell icon updates whenever the sheet's contents change. --- ## Cache — `util/NotificationsCache` Encrypted JSON files in `filesDir` (via `CacheEncryption`), one per bank+loginId. Stores the full notifications payload plus a separate "read IDs" set. Cleared along with the rest of the per-account history when the user invokes [Settings → Storage → Clear All Caches](17-settings-storage.md) (read state is excluded from that clear — verified in `17-settings-storage.md`). --- ## Opt-In The polling service is not started automatically. The user must: 1. Tap Settings → Notifications 2. Toggle the switch on 3. Grant `POST_NOTIFICATIONS` (API 33+) 4. Optionally allow battery-optimisation exemption See [Settings → Notifications](27-settings-notifications.md) for the full flow. ---   --- [← Tap to Pay](23-tap-to-pay.md)     **Next →** [QR Scanner](25-qr-scanner.md)