feat(core): implement dynamic app whitelisting, deduplication, and SA…#453
feat(core): implement dynamic app whitelisting, deduplication, and SA…#453hitmanM340 wants to merge 3 commits into
Conversation
…F backups - feat: Added dynamic package verification in BankNotificationListenerService - feat: Added GPayParser to support peer-to-peer 'paid you' UPI alerts - feat: Refactored BackupExporter to use Storage Access Framework DocumentFile API - fix: Resolved race condition duplicate records using a ±60s repository window filter - fix: Set android:exported='true' for listener registration in AndroidManifest
Greptile SummaryThis PR adds four related features: a GPay P2P notification parser, transaction deduplication in
Confidence Score: 4/5Safe to merge after fixing GPayParser's base class — all other changes are well-structured and the encryption regression from the previous review is resolved. The only blocking concern is GPayParser extending BankParser directly instead of BaseIndianBankParser. The project's architecture rules explicitly require all Indian bank/payment parsers to extend BaseIndianBankParser so they inherit mandate, subscription, and balance-update handling. GPay is an Indian UPI service, so this parser will silently skip that shared logic until the base class is corrected. Everything else — the encryption implementation, the backward-compatible import fallback, the notification pre-check fix, and the SAF backup flow — looks correct. parser-core/src/main/kotlin/com/pennywiseai/parser/core/bank/GPayParser.kt needs its base class changed to BaseIndianBankParser. Important Files Changed
Sequence DiagramsequenceDiagram
participant App as Bank App
participant Listener as BankNotificationListenerService
participant Config as BankNotificationConfig
participant Prefs as UserPreferencesRepository
participant Parser as BankParserFactory/GPayParser
participant Repo as TransactionRepository
App->>Listener: onNotificationPosted(sbn)
Listener->>Config: isSupportedPackage(packageName)
alt not in static allowedPackages
Config-->>Listener: false → return
end
Note over Listener: Extract title / text / bigText<br/>Compose messageBody (GPay: title+text, others: bigText)
Listener->>Prefs: monitoredBankPackages.first()
Prefs-->>Listener: "Set<String>"
alt package not whitelisted
Listener-->>Listener: "return@launch"
end
Listener->>Parser: getParser(senderAlias).parse(messageBody)
Parser-->>Listener: ParsedTransaction?
Listener->>Repo: insertTransaction(entity)
Repo->>Repo: deduplication ±60s window
alt duplicate found
Repo-->>Listener: -1L
else
Repo-->>Listener: rowId
end
Reviews (2): Last reviewed commit: "Fix release build crash by adding SQLCip..." | Re-trigger Greptile |
| val title = sbn.notification.extras?.getCharSequence(android.app.Notification.EXTRA_TITLE)?.toString()?.trim().orEmpty() | ||
| val text = sbn.notification.extras?.getCharSequence(android.app.Notification.EXTRA_TEXT)?.toString()?.trim().orEmpty() | ||
| val body = if (title.isNotEmpty() && text.isNotEmpty()) { | ||
| "$title $text".trim() | ||
| } else { | ||
| val extracted = BankNotificationConfig.extractMessage(sbn.notification) | ||
| extracted.ifBlank { title.ifBlank { text } } | ||
| } |
There was a problem hiding this comment.
EXTRA_BIG_TEXT is silently dropped for all bank notifications that have both title and text
When both EXTRA_TITLE and EXTRA_TEXT are non-blank — which is the common case for bank notifications — the body is set to "$title $text" and EXTRA_BIG_TEXT is never consulted. The previous BankNotificationConfig.extractMessage prioritised EXTRA_BIG_TEXT, which carries the full transaction detail line (account number, reference ID, balance) for Enpara, Faysal Bank, and SBI notifications. With this change those parsers receive only the truncated EXTRA_TEXT string instead of the full text they need, silently producing parse failures or partial transactions. The GPay title-concatenation fix should be applied only when senderAlias == "GPay", or after a failed parse on the big-text path.
| endDate: LocalDateTime | ||
| ): Double? = transactionDao.getTotalAmountByTypeAndPeriod(type, startDate, endDate) | ||
|
|
||
| suspend fun insertTransaction(transaction: TransactionEntity): Long = | ||
| transactionDao.insertTransaction(transaction) | ||
| suspend fun insertTransaction(transaction: TransactionEntity): Long { | ||
| val windowStart = transaction.dateTime.minusSeconds(60) | ||
| val windowEnd = transaction.dateTime.plusSeconds(60) | ||
|
|
||
| val duplicates = transactionDao.getTransactionByAmountAndDate(transaction.amount, windowStart, windowEnd) | ||
| val hasDuplicate = duplicates.any { existing -> | ||
| existing.transactionType == transaction.transactionType && existing.id != transaction.id | ||
| } | ||
|
|
||
| if (hasDuplicate) { | ||
| return -1L | ||
| } | ||
|
|
||
| return transactionDao.insertTransaction(transaction) | ||
| } |
There was a problem hiding this comment.
Deduplication bypass via bulk insert
The duplicate-detection window is only applied in insertTransaction (single-row path). insertTransactions (the bulk import path used by BackupImporter.mergeData and historical SMS scan) skips it entirely, so a user who manually triggers two back-to-back imports or a historical scan that overlaps with live notifications can still produce duplicates. Extracting the duplicate check into a shared helper and calling it from both insert paths would close this gap.
|
Thanks @hitmanM340 — lots here and it's in good shape (Greptile 4/5); the earlier encryption regression is resolved. One blocking item it flagged: It's the oldest of the open contributor PRs and has fallen behind Gentle housekeeping heads-up: if it stays quiet for ~another week we may close it just to keep the queue tidy — definitely not a rejection given how close this is, so reopen/ping anytime and we'll pick it back up. 🙏 |
Summary
This Pull Request implements updates to address real-world notification parsing gaps (Gpay) and backup needs (Every 7 days, next update to set custom timer):
insertTransactioninTransactionRepository.ktto check for duplicate transactions matching the same type and amount within a ±60-second window, returning-1Lon match to abort insertion.GPayParser.ktto parse incoming peer-to-peer UPI payments (e.g.[Sender] paid you [Amount]) asTransactionType.INCOME.BankParserFactory.kt.BankNotificationListenerService.ktto dynamically concatenate notification titles and texts (e.g."${title} ${text}") before parsing, correcting cases where Google Pay splits sender name and transaction action into separate fields.monitored_bank_packagesstring set preference toUserPreferencesRepository.kt.com.google.android.apps.nbu.paisa.user) if the configuration set is empty.AndroidManifest.xmlby settingandroid:exported="true"onBankNotificationListenerService.backup_directory_uripreference mappings.BackupExporter.ktto supportexportToUriusing the AndroidDocumentFileAPI.AutoBackupWorker.ktto export backups to the custom tree URI with fallback to default storage and cleanup older documents (keeping the last 3 files).Uri.parse()calls.Folderlauncher inSettingsScreen.ktutilizing persistable URI permissions, ensuring the settings UI renders properly immediately on fresh boot.Type of change
Screenshots/Recordings (optional)
Click to expand layout screenshots (Samsung M33 5G)
How to test
./gradlew :parser-core:testto verify thatGPayParserTestpasses cleanly../gradlew testsuite to ensure all project unit tests pass and compile../gradlew installStandardDebugand confirm:Breaking changes
Related issues
Direct contribution.
Checklist
./gradlew testpasses locally./gradlew lintpasses locally