diff --git a/README.md b/README.md
index 451a625..9f9d437 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,11 @@ This notification listener app acts as a bridge between your Android device's no
## Features
-- Captures notifications as they appear
-- Rule-based filtering by package name, title regex, and text regex
-- Sends structured JSON data to webhook endpoints
-- Manual retry for failed or undecided notifications
+- **Real-time notification capture** - Listens to Android system notifications as they appear
+- **Smart filtering system** - Rule-based filtering by package name with regex support
+- **Webhook integration** - Sends structured JSON data to configurable webhook endpoints
+- **Offline resilience** - Stores failed notifications locally for manual retry
+- **Undecided notification handling** - Captures notifications that don't match any rules for manual processing
## Technical Architecture
@@ -19,7 +20,9 @@ graph TD
B --> C[Service Layer
NotificationListenerService]
C --> D[Repository Layer
NotificationRepository]
D --> E[Network Layer
WebhookApi]
- D --> F[Database Layer
AppDatabase]
+ D --> F[Database Layer
FailedNotification + UndecidedNotification]
+ C --> G[Configuration Layer
NotificationFilterEngine]
+ G --> H[Config Data
DefaultWebhookConfig]
```
### Data Flow
@@ -31,19 +34,22 @@ flowchart TD
C --> D[NotificationFilterEngine.isIgnored]
D --> E{Is Ignored?}
- E -->|Yes| F[Skip Notification]
+ E -->|Yes| F[Skip Notification
Black-holed]
E -->|No| G[NotificationFilterEngine.findMatchingUrls]
G --> H{Has Matching URLs?}
- H -->|No| I[Store as Undecided Notification]
- H -->|Yes| J[Send to Webhook URLs]
+ H -->|No| I[Store as Undecided Notification
Room Database]
+ H -->|Yes| J[Send to Webhook URLs
Retrofit HTTP POST]
J --> K{Send Successful?}
- K -->|Yes| L[Continue Processing]
- K -->|No| M[Store as Failed Notification]
+ K -->|Yes| L[Continue Processing
Success]
+ K -->|No| M[Store as Failed Notification
Room Database]
- I --> N[Available for Manual Upload]
- M --> O[Available for Retry]
+ I --> N[Available for Manual Upload
NotificationListActivity]
+ M --> O[Available for Retry
NotificationListActivity]
+
+ N --> P[User Selects Webhook URL
Manual Processing]
+ O --> Q[User Retries Failed Request
Bulk Operations]
```
## JSON Payload Format
@@ -63,16 +69,38 @@ Notifications are sent to webhooks as JSON with the following structure:
## Installation & Setup
-1. Build the APK:
+### Prerequisites
+
+- **Android 15+ (API level 35+)** - The app requires the latest Android version
+- **Android Studio** - For development and building
+- **ADB** - For installing the APK
+
+### Build & Install
+
+1. **Configure webhook URL** (optional):
+
+```bash
+export WEBHOOK_URL_BANK="https://your-n8n-instance.com/webhook/your-webhook-id"
+```
+
+2. **Build the APK**:
```bash
+# Debug build
./gradlew assembleDebug
+
+# Release build (requires keystore configuration)
+./gradlew assembleRelease
```
-2. Install on device (Android 15+):
+3. **Install on device**:
```bash
+# Debug APK
adb install app/build/outputs/apk/debug/app-debug.apk
+
+# Release APK
+adb install app/build/outputs/apk/release/app-release.apk
```
3. Grant Notification Access:
@@ -84,6 +112,18 @@ adb install app/build/outputs/apk/debug/app-debug.apk
## Development
+### Technology Stack
+
+- **Language**: Kotlin
+- **UI Framework**: Jetpack Compose
+- **Architecture**: MVVM with Repository Pattern
+- **Dependency Injection**: Hilt
+- **Database**: Room (SQLite)
+- **Networking**: Retrofit + OkHttp
+- **JSON Serialization**: Gson
+- **Testing**: JUnit 4, MockK, Coroutines Test
+- **Build System**: Gradle with Kotlin DSL
+
### Project Structure
```
@@ -104,5 +144,6 @@ app/src/main/java/com/daohoangson/n8n/notificationlistener/
The project includes comprehensive unit tests:
```bash
+# Run all unit tests
./gradlew test
```
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3c2fb92..5d9b9ff 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -18,15 +18,30 @@ android {
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField(
+ "String",
+ "WEBHOOK_URL_BANK",
+ System.getenv("WEBHOOK_URL_BANK") ?: "\"https://n8n.cloud/webhook/bank\""
+ )
+ }
+
+ signingConfigs {
+ create("release") {
+ storeFile = file("keystore.jks")
+ storePassword = System.getenv("ANDROID_KEYSTORE_PASSWORD")
+ keyAlias = System.getenv("ANDROID_KEY_ALIAS")
+ keyPassword = System.getenv("ANDROID_KEY_PASSWORD")
+ }
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
+ getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
)
+ signingConfig = signingConfigs.getByName("release")
}
}
compileOptions {
@@ -76,7 +91,7 @@ dependencies {
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.androidx.room.testing)
testImplementation(libs.okhttp.mockwebserver)
-
+
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
diff --git a/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngine.kt b/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngine.kt
index 03cb541..d5b45b4 100644
--- a/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngine.kt
+++ b/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngine.kt
@@ -6,45 +6,20 @@ import javax.inject.Singleton
@Singleton
class NotificationFilterEngine @Inject constructor() {
-
+
private val config = DefaultWebhookConfig.config
-
+
fun isIgnored(notificationData: NotificationData): Boolean {
- return config.ignoredPackages.contains(notificationData.packageName)
- }
-
- fun findMatchingUrls(notificationData: NotificationData): List {
- return config.urls.filter { webhookUrl ->
- webhookUrl.rules.any { rule ->
- matchesRule(notificationData, rule)
- }
+ return config.ignoredPackages.any { regex ->
+ regex.matches(notificationData.packageName)
}
}
-
- private fun matchesRule(notificationData: NotificationData, rule: FilterRule): Boolean {
- if (rule.packageName != notificationData.packageName) {
- return false
- }
-
- if (rule.titleRegex == null && rule.textRegex == null) {
- return true
- }
-
- rule.titleRegex?.let { titleRegex ->
- val title = notificationData.title ?: ""
- if (!titleRegex.matches(title)) {
- return false
- }
- }
-
- rule.textRegex?.let { textRegex ->
- val text = notificationData.text ?: ""
- if (!textRegex.matches(text)) {
- return false
+
+ fun findMatchingUrls(notificationData: NotificationData): List {
+ return config.urls.filter {
+ it.packages.any { regex ->
+ regex.matches(notificationData.packageName)
}
}
-
- return true
}
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/WebhookConfig.kt b/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/WebhookConfig.kt
index a212283..b275164 100644
--- a/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/WebhookConfig.kt
+++ b/app/src/main/java/com/daohoangson/n8n/notificationlistener/config/WebhookConfig.kt
@@ -1,64 +1,58 @@
package com.daohoangson.n8n.notificationlistener.config
+import com.daohoangson.n8n.notificationlistener.BuildConfig
+
data class WebhookConfig(
- val urls: List,
- val ignoredPackages: List
+ val urls: List, val ignoredPackages: List
)
data class WebhookUrl(
- val url: String,
- val name: String,
- val rules: List
-)
-
-data class FilterRule(
- val packageName: String,
- val titleRegex: Regex? = null,
- val textRegex: Regex? = null
+ val url: String, val name: String, val packages: List
)
object DefaultWebhookConfig {
val config = WebhookConfig(
urls = listOf(
WebhookUrl(
- url = "https://n8n.cloud/webhook/slack-notifications",
- name = "Slack Notifications",
- rules = listOf(
- FilterRule(packageName = "com.slack"),
- FilterRule(packageName = "com.microsoft.teams")
- )
- ),
- WebhookUrl(
- url = "https://n8n.cloud/webhook/social-media",
- name = "Social Media",
- rules = listOf(
- FilterRule(packageName = "com.instagram.android"),
- FilterRule(packageName = "com.twitter.android"),
- FilterRule(
- packageName = "com.facebook.katana",
- titleRegex = ".*mentioned you.*".toRegex()
- )
+ url = BuildConfig.WEBHOOK_URL_BANK, name = "Bank apps", packages = listOf(
+ Regex.fromLiteral("com.bplus.vtpay"),
+ Regex.fromLiteral("com.evnhcmc.evnmobileapp"),
+ Regex.fromLiteral("com.mservice.momotransfer"),
+ Regex.fromLiteral("com.VCB"),
+ Regex.fromLiteral("com.vib.myvib2"),
+ Regex.fromLiteral("vn.com.techcombank.bb.app"),
+ Regex.fromLiteral("vn.com.vng.zalopay")
)
),
- WebhookUrl(
- url = "https://n8n.cloud/webhook/urgent-alerts",
- name = "Urgent Alerts",
- rules = listOf(
- FilterRule(
- packageName = "com.android.phone",
- titleRegex = ".*Emergency.*".toRegex()
- ),
- FilterRule(
- packageName = "com.banking.app",
- textRegex = ".*fraud.*|.*suspicious.*".toRegex(RegexOption.IGNORE_CASE)
- )
- )
- )
- ),
- ignoredPackages = listOf(
- "com.android.systemui",
- "com.google.android.gms",
- "com.android.providers.downloads"
+ ), ignoredPackages = listOf(
+ // chat
+ Regex.fromLiteral("com.discord"),
+ Regex.fromLiteral("com.facebook.orca"),
+ Regex.fromLiteral("com.Slack"),
+ Regex.fromLiteral("org.telegram.messenger"),
+ Regex.fromLiteral("org.twitter.android"),
+ Regex.fromLiteral("com.whatsapp"),
+ Regex.fromLiteral("com.zing.zalo"),
+ // social
+ Regex.fromLiteral("com.facebook.katana"),
+ Regex("^com\\.instagram.*"),
+ Regex.fromLiteral("com.linkedin.android"),
+ // system
+ Regex.fromLiteral("android"),
+ Regex("^com\\.android.*"),
+ Regex("^com\\.google.*"),
+ Regex("^com\\.osp.*"),
+ Regex("^com\\.samsung.*"),
+ Regex("^com\\.sec.*"),
+ // others
+ Regex.fromLiteral("com.echo.global.app"),
+ Regex.fromLiteral("com.glow.android.baby"),
+ Regex.fromLiteral("com.grabtaxi.passenger"),
+ Regex.fromLiteral("com.microsoft.office.outlook"),
+ Regex("^com.netflix.*"),
+ Regex.fromLiteral("com.nordvpn.android"),
+ Regex.fromLiteral("com.openai.chatgpt"),
+ Regex.fromLiteral("com.viettel.ViettelPost"),
)
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daohoangson/n8n/notificationlistener/data/database/AppDatabase.kt b/app/src/main/java/com/daohoangson/n8n/notificationlistener/data/database/AppDatabase.kt
index e87b8c5..841919b 100644
--- a/app/src/main/java/com/daohoangson/n8n/notificationlistener/data/database/AppDatabase.kt
+++ b/app/src/main/java/com/daohoangson/n8n/notificationlistener/data/database/AppDatabase.kt
@@ -14,18 +14,18 @@ import com.daohoangson.n8n.notificationlistener.utils.Constants
abstract class AppDatabase : RoomDatabase() {
abstract fun failedNotificationDao(): FailedNotificationDao
abstract fun undecidedNotificationDao(): UndecidedNotificationDao
-
+
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
-
+
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
Constants.DATABASE_NAME
- ).fallbackToDestructiveMigration()
+ ).fallbackToDestructiveMigration(dropAllTables = true)
.build()
INSTANCE = instance
instance
diff --git a/app/src/main/java/com/daohoangson/n8n/notificationlistener/network/NetworkModule.kt b/app/src/main/java/com/daohoangson/n8n/notificationlistener/network/NetworkModule.kt
index 1d16733..9bfe774 100644
--- a/app/src/main/java/com/daohoangson/n8n/notificationlistener/network/NetworkModule.kt
+++ b/app/src/main/java/com/daohoangson/n8n/notificationlistener/network/NetworkModule.kt
@@ -20,6 +20,7 @@ object NetworkModule {
.build()
private val retrofit = Retrofit.Builder()
+ .baseUrl("https://google.com")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
diff --git a/app/src/test/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngineTest.kt b/app/src/test/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngineTest.kt
index 8941bc0..e97e881 100644
--- a/app/src/test/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngineTest.kt
+++ b/app/src/test/java/com/daohoangson/n8n/notificationlistener/config/NotificationFilterEngineTest.kt
@@ -8,9 +8,13 @@ class NotificationFilterEngineTest {
private val filterEngine = NotificationFilterEngine()
+ // ========================================
+ // Tests for isIgnored() method
+ // ========================================
+
@Test
- fun isIgnored_shouldReturnTrue_forIgnoredPackages() {
- val notificationData = NotificationData(
+ fun isIgnored_shouldReturnTrue_forSystemPackages() {
+ val systemNotification = NotificationData(
packageName = "com.android.systemui",
title = "Test Title",
text = "Test Message",
@@ -18,207 +22,205 @@ class NotificationFilterEngineTest {
id = 1,
tag = null
)
-
- assertTrue(filterEngine.isIgnored(notificationData))
+
+ assertTrue(filterEngine.isIgnored(systemNotification))
}
@Test
- fun isIgnored_shouldReturnFalse_forNonIgnoredPackages() {
- val notificationData = NotificationData(
- packageName = "com.slack",
+ fun isIgnored_shouldReturnTrue_forGoogleServicesPackages() {
+ val gmsNotification = NotificationData(
+ packageName = "com.google.android.gms",
title = "Test Title",
text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- assertFalse(filterEngine.isIgnored(notificationData))
+
+ assertTrue(filterEngine.isIgnored(gmsNotification))
}
@Test
- fun findMatchingUrls_shouldReturnEmpty_forIgnoredPackages() {
- val notificationData = NotificationData(
- packageName = "com.google.android.gms",
+ fun isIgnored_shouldReturnTrue_forSocialMediaApps() {
+ // Test Slack
+ val slackNotification = NotificationData(
+ packageName = "com.Slack",
title = "Test Title",
text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertTrue(matchingUrls.isEmpty())
- }
+ assertTrue(filterEngine.isIgnored(slackNotification))
- @Test
- fun findMatchingUrls_shouldReturnMatchingUrl_forPackageNameMatch() {
- val notificationData = NotificationData(
- packageName = "com.slack",
- title = "Test Title",
+ // Test Facebook Messenger
+ val messengerNotification = NotificationData(
+ packageName = "com.facebook.orca",
+ title = "John mentioned you in a comment",
text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Slack Notifications", matchingUrls[0].name)
- }
+ assertTrue(filterEngine.isIgnored(messengerNotification))
- @Test
- fun findMatchingUrls_shouldReturnEmpty_forNonMatchingPackage() {
- val notificationData = NotificationData(
- packageName = "com.nonexistent.app",
- title = "Test Title",
+ // Test Facebook
+ val facebookNotification = NotificationData(
+ packageName = "com.facebook.katana",
+ title = "John posted a photo",
text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertTrue(matchingUrls.isEmpty())
- }
+ assertTrue(filterEngine.isIgnored(facebookNotification))
- @Test
- fun findMatchingUrls_shouldMatchTitleRegex() {
- val notificationData = NotificationData(
- packageName = "com.facebook.katana",
- title = "John mentioned you in a comment",
- text = "Test Message",
+ // Test Discord
+ val discordNotification = NotificationData(
+ packageName = "com.discord",
+ title = "New message",
+ text = "You have a new direct message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Social Media", matchingUrls[0].name)
- }
+ assertTrue(filterEngine.isIgnored(discordNotification))
- @Test
- fun findMatchingUrls_shouldNotMatch_whenTitleRegexFails() {
- val notificationData = NotificationData(
- packageName = "com.facebook.katana",
- title = "John posted a photo",
- text = "Test Message",
+ // Test Telegram
+ val telegramNotification = NotificationData(
+ packageName = "org.telegram.messenger",
+ title = "Any title",
+ text = "Any text",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertTrue(matchingUrls.isEmpty())
+ assertTrue(filterEngine.isIgnored(telegramNotification))
}
+ // ========================================
+ // Tests for findMatchingUrls() method - Bank Apps
+ // ========================================
+
@Test
- fun findMatchingUrls_shouldMatchTextRegex_caseInsensitive() {
- val notificationData = NotificationData(
- packageName = "com.banking.app",
+ fun findMatchingUrls_shouldMatchBankApps() {
+ // Test VCB
+ val vcbNotification = NotificationData(
+ packageName = "com.VCB",
title = "Security Alert",
text = "FRAUD detected on your account",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Urgent Alerts", matchingUrls[0].name)
- }
-
- @Test
- fun findMatchingUrls_shouldMatchTextRegex_suspicious() {
- val notificationData = NotificationData(
- packageName = "com.banking.app",
- title = "Security Alert",
- text = "Suspicious activity detected",
+ val vcbUrls = filterEngine.findMatchingUrls(vcbNotification)
+ assertEquals(1, vcbUrls.size)
+ assertEquals("Bank apps", vcbUrls[0].name)
+
+ // Test Techcombank
+ val techcombankNotification = NotificationData(
+ packageName = "vn.com.techcombank.bb.app",
+ title = "- VND 100,000",
+ text = "Thẻ 4 .... .... 1234\\nChi tiêu: - VND 100,000 vào 01/01/2025 lúc 00:00:00 tại GOOGLE...\\nHạn mức khả dụng: VND 123,456,789",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Urgent Alerts", matchingUrls[0].name)
- }
-
- @Test
- fun findMatchingUrls_shouldNotMatch_whenTextRegexFails() {
- val notificationData = NotificationData(
- packageName = "com.banking.app",
- title = "Account Update",
- text = "Your balance has been updated",
+ val techcombankUrls = filterEngine.findMatchingUrls(techcombankNotification)
+ assertEquals(1, techcombankUrls.size)
+ assertEquals("Bank apps", techcombankUrls[0].name)
+
+ // Test MoMo
+ val momoNotification = NotificationData(
+ packageName = "com.mservice.momotransfer",
+ title = "Payment Notification",
+ text = "You have received money",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertTrue(matchingUrls.isEmpty())
+ val momoUrls = filterEngine.findMatchingUrls(momoNotification)
+ assertEquals(1, momoUrls.size)
+ assertEquals("Bank apps", momoUrls[0].name)
+
+ // Test MyVIB
+ val vibNotification = NotificationData(
+ packageName = "com.vib.myvib2",
+ title = "Transaction Alert",
+ text = "Your account has been debited",
+ timestamp = System.currentTimeMillis(),
+ id = 1,
+ tag = null
+ )
+ val vibUrls = filterEngine.findMatchingUrls(vibNotification)
+ assertEquals(1, vibUrls.size)
+ assertEquals("Bank apps", vibUrls[0].name)
}
+ // ========================================
+ // Tests for findMatchingUrls() method - Empty Results
+ // ========================================
+
@Test
- fun findMatchingUrls_shouldHandleNullTitle() {
- val notificationData = NotificationData(
- packageName = "com.facebook.katana",
- title = null,
+ fun findMatchingUrls_shouldReturnEmpty_forNonMatchingPackages() {
+ val nonMatchingNotification = NotificationData(
+ packageName = "com.nonexistent.app",
+ title = "Test Title",
text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
+
+ val matchingUrls = filterEngine.findMatchingUrls(nonMatchingNotification)
assertTrue(matchingUrls.isEmpty())
}
@Test
- fun findMatchingUrls_shouldHandleNullText() {
- val notificationData = NotificationData(
+ fun findMatchingUrls_shouldReturnEmpty_forUnconfiguredBankApp() {
+ val unconfiguredBankNotification = NotificationData(
packageName = "com.banking.app",
- title = "Security Alert",
- text = null,
+ title = "Account Update",
+ text = "Your balance has been updated",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
+
+ val matchingUrls = filterEngine.findMatchingUrls(unconfiguredBankNotification)
assertTrue(matchingUrls.isEmpty())
}
+ // ========================================
+ // Tests for findMatchingUrls() method - Edge Cases
+ // ========================================
+
@Test
- fun findMatchingUrls_shouldMatchMultipleUrls_forSamePackage() {
- val notificationData = NotificationData(
- packageName = "com.instagram.android",
- title = "New message",
- text = "You have a new direct message",
+ fun findMatchingUrls_shouldHandleNullTitleAndText() {
+ // Test null title
+ val nullTitleNotification = NotificationData(
+ packageName = "com.VCB",
+ title = null,
+ text = "Test Message",
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Social Media", matchingUrls[0].name)
- }
+ val nullTitleUrls = filterEngine.findMatchingUrls(nullTitleNotification)
+ assertEquals(1, nullTitleUrls.size)
+ assertEquals("Bank apps", nullTitleUrls[0].name)
- @Test
- fun findMatchingUrls_shouldMatch_withNoRegexRules() {
- val notificationData = NotificationData(
- packageName = "com.microsoft.teams",
- title = "Any title",
- text = "Any text",
+ // Test null text
+ val nullTextNotification = NotificationData(
+ packageName = "com.VCB",
+ title = "Security Alert",
+ text = null,
timestamp = System.currentTimeMillis(),
id = 1,
tag = null
)
-
- val matchingUrls = filterEngine.findMatchingUrls(notificationData)
- assertEquals(1, matchingUrls.size)
- assertEquals("Slack Notifications", matchingUrls[0].name)
+ val nullTextUrls = filterEngine.findMatchingUrls(nullTextNotification)
+ assertEquals(1, nullTextUrls.size)
+ assertEquals("Bank apps", nullTextUrls[0].name)
}
}
\ No newline at end of file