Skip to content

feat(core): implement dynamic app whitelisting, deduplication, and SA…#453

Open
hitmanM340 wants to merge 3 commits into
sarim2000:mainfrom
hitmanM340:main
Open

feat(core): implement dynamic app whitelisting, deduplication, and SA…#453
hitmanM340 wants to merge 3 commits into
sarim2000:mainfrom
hitmanM340:main

Conversation

@hitmanM340

Copy link
Copy Markdown

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):

  1. Transaction Deduplication: Updated insertTransaction in TransactionRepository.kt to check for duplicate transactions matching the same type and amount within a ±60-second window, returning -1L on match to abort insertion.
  2. Google Pay UPI P2P Parser & Concatenation:
    • Created GPayParser.kt to parse incoming peer-to-peer UPI payments (e.g. [Sender] paid you [Amount]) as TransactionType.INCOME.
    • Registered the parser inside BankParserFactory.kt.
    • Updated BankNotificationListenerService.kt to 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.
  3. Dynamic App Whitelist Manager:
    • Added a monitored_bank_packages string set preference to UserPreferencesRepository.kt.
    • Updated the listener service to query the whitelist on a background thread and default to whitelisting Google Pay (com.google.android.apps.nbu.paisa.user) if the configuration set is empty.
    • Fixed a critical system binding issue in AndroidManifest.xml by setting android:exported="true" on BankNotificationListenerService.
    • Added an interactive whitelist selection dialog and row inside the settings UI.
  4. Storage Access Framework (SAF) Custom Backup Destination:
    • Added backup_directory_uri preference mappings.
    • Refactored BackupExporter.kt to support exportToUri using the Android DocumentFile API.
    • Configured AutoBackupWorker.kt to export backups to the custom tree URI with fallback to default storage and cleanup older documents (keeping the last 3 files).
    • Added strict string checks to prevent empty/blank Uri.parse() calls.
    • Exposed path configuration options and a Folder launcher in SettingsScreen.kt utilizing persistable URI permissions, ensuring the settings UI renders properly immediately on fresh boot.

Type of change

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation update
  • refactor: Code improvement (no behavior change)
  • perf: Performance improvement
  • style: Formatting (no behavior change)
  • test: Add/update tests
  • chore/ci: Build or CI changes

Screenshots/Recordings (optional)

Click to expand layout screenshots (Samsung M33 5G)
1. Auto-Backup Features 2. Auto-Backup Pass Setup
3. Bank Notification Access 4. Monitored Bank Apps (Pulls From Code)

How to test

  1. Run ./gradlew :parser-core:test to verify that GPayParserTest passes cleanly.
  2. Run the full ./gradlew test suite to ensure all project unit tests pass and compile.
  3. Deploy to a physical device or emulator via ./gradlew installStandardDebug and confirm:
    • The settings screen displays the "Monitored Bank Apps" and "Backup Location" rows immediately on launch.
    • The "Monitored Bank Apps" checklist dialog displays default bank options and updates preferences correctly.
    • The "Backup Location" launches the system folder tree picker and updates the folder URI correctly.

Breaking changes

  • No breaking changes
  • Yes (describe):

Related issues

Direct contribution.

Checklist

  • Focused PR (single purpose)
  • Tests added/updated (if applicable)
  • Docs updated (if applicable)
  • ./gradlew test passes locally
  • ./gradlew lint passes locally
  • Follows existing code style and patterns

…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-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds four related features: a GPay P2P notification parser, transaction deduplication in insertTransaction, a dynamic app-whitelist for the notification listener, and an encrypted SAF-based backup system (with password-protected export/import and a 7-day auto-backup worker).

  • GPay parser & notification fix: GPayParser detects "[Sender] paid you [Amount]" notifications as INCOME; the listener service now isolates title+text concatenation to GPay only, preserving EXTRA_BIG_TEXT for other banks like SBI.
  • Encrypted backups: BackupEncryptor uses AES-GCM + PBKDF2-SHA256 (600k iterations); BackupImporter retains a plaintext fallback for pre-existing backup files, restoring backward compatibility.
  • Dynamic whitelist: Monitored packages are stored in DataStore as a StringSet, surfaced in a Settings dialog, and the listener performs a fast synchronous pre-check before any coroutine is launched.

Confidence Score: 4/5

Safe 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

Filename Overview
parser-core/src/main/kotlin/com/pennywiseai/parser/core/bank/GPayParser.kt New GPay P2P notification parser — extends BankParser directly instead of BaseIndianBankParser, violating the project's mandatory hierarchy for Indian bank/payment parsers.
app/src/main/java/com/pennywiseai/tracker/receiver/BankNotificationListenerService.kt GPay title+text concatenation isolated to GPay package only; EXTRA_BIG_TEXT preserved for other banks. Fast synchronous pre-check restored before the coroutine launch. Minor dead-code condition in the pre-check guard.
app/src/main/java/com/pennywiseai/tracker/data/backup/BackupEncryptor.kt New AES-GCM backup encryptor with PBKDF2-HMAC-SHA256 at 600,000 iterations, random salt+IV, and a magic-byte header. Format and iteration count are sound.
app/src/main/java/com/pennywiseai/tracker/data/backup/BackupImporter.kt Backward-compatible decrypt path added — detects magic header for new encrypted backups and falls back to plain-JSON for old backups, fixing the previously reported import regression.
app/src/main/java/com/pennywiseai/tracker/worker/AutoBackupWorker.kt 7-day periodic backup worker with SAF primary path and local-storage fallback; cleans up older files keeping the last 3 backups in both paths.
app/src/main/java/com/pennywiseai/tracker/data/repository/TransactionRepository.kt insertTransaction now checks for duplicate transactions (same type, same amount, ±60 s window) and returns -1L on match.
app/src/main/java/com/pennywiseai/tracker/receiver/BankNotificationConfig.kt Added GPay and SBI package entries to allowedPackages; new isSupportedPackage method is a duplicate of existing isAllowed.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (2): Last reviewed commit: "Fix release build crash by adding SQLCip..." | Re-trigger Greptile

Comment on lines +55 to +62
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 } }
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Comment thread app/src/main/java/com/pennywiseai/tracker/data/backup/BackupEncryptor.kt Outdated
Comment on lines 104 to +121
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)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

@sarim2000

Copy link
Copy Markdown
Owner

Thanks @hitmanM340 — lots here and it's in good shape (Greptile 4/5); the earlier encryption regression is resolved. One blocking item it flagged: GPayParser extends BankParser directly, but GPay is an Indian UPI service so it needs to extend BaseIndianBankParser — otherwise it silently skips the shared mandate/subscription/balance-update handling the architecture relies on.

It's the oldest of the open contributor PRs and has fallen behind main with conflicts — could you rebase when you get a chance? That'll re-trigger Greptile too.

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. 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants