3.8 KiB
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.
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
onCreate: creates the service channel (low importance, no badge), callsstartForeground(SERVICE_NOTIF_ID, ...)with a persistent low-priority notification, then starts the polling looponStartCommand: returnsSTART_STICKYso Android relaunches the service after killsonDestroy: 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+ViewPager2with one page per active bank login - Each page is a
RecyclerViewofAppNotificationgrouped 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 (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:
- Tap Settings → Notifications
- Toggle the switch on
- Grant
POST_NOTIFICATIONS(API 33+) - Optionally allow battery-optimisation exemption
See Settings → Notifications for the full flow.
← Tap to Pay Next → QR Scanner