Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.aboutlibraries.android)
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("com.google.devtools.ksp") version "2.3.3"
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.kotlin.serialization)
}

Expand All @@ -18,8 +19,8 @@ android {
applicationId = "co.adityarajput.notifilter"
minSdk = 29
targetSdk = 36
versionCode = 22
versionName = "4.5.0"
versionCode = 23
versionName = "4.6.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -86,6 +87,7 @@ dependencies {
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)
implementation(libs.aboutlibraries.compose)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
android:resource="@xml/widget_info" />
</receiver>


<service
android:name=".services.NotificationListener"
android:exported="true"
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/co/adityarajput/notifilter/Constants.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package co.adityarajput.notifilter

object Constants {
const val STATE = "state"
const val WIDGET_PREVIEW_SET_AT = "widget_preview_set_at"

const val SETTINGS = "settings"
const val RUN_IN_FOREGROUND = "run_in_foreground"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package co.adityarajput.notifilter
import android.app.Application
import android.content.pm.ApplicationInfo
import co.adityarajput.notifilter.data.AppContainer
import co.adityarajput.notifilter.utils.pushWidgetPreview
import co.adityarajput.notifilter.utils.setWidgetPreview
import co.adityarajput.notifilter.utils.subscribeWidgetToFlow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand All @@ -24,7 +24,7 @@ class NotiFilterApplication : Application() {
}

CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
this@NotiFilterApplication.pushWidgetPreview()
this@NotiFilterApplication.setWidgetPreview()
subscribeWidgetToFlow(this@NotiFilterApplication, container.repository.log())
}
}
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/java/co/adityarajput/notifilter/data/AppContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ class AppContainer(private val context: Context) {
historyEnabled = false,
),
Filter(
App("Instagram", "com.instagram.android"),
"reels",
App("Tumblr", "com.tumblr"),
"poll",
Action.MUTE,
RegexTarget.TITLE,
historyEnabled = false,
RegexTarget.CONTENT,
hits = 17,
widgetEnabled = true,
),
Filter(
App("F-Droid", "org.fdroid"),
Expand Down Expand Up @@ -130,6 +131,12 @@ class AppContainer(private val context: Context) {
"WhatsApp",
System.currentTimeMillis() - 37 * 60 * 1000,
),
Notification(
"Poll finale alert",
"Check the poll results for \"Do you pronounce Z as Zed?\".",
"Tumblr",
System.currentTimeMillis() - 3 * 60 * 1000,
),
)
}
}
Expand Down
12 changes: 8 additions & 4 deletions app/src/main/java/co/adityarajput/notifilter/data/Cache.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,29 @@ package co.adityarajput.notifilter.data
import android.content.Intent
import android.content.pm.PackageManager
import co.adityarajput.notifilter.data.models.App
import co.adityarajput.notifilter.data.models.Intents
import co.adityarajput.notifilter.utils.Logger
import java.util.Collections.synchronizedMap

object Cache {
private var _allPackages: List<App>? = null
private var _visibleApps: List<App>? = null

private var cachedAt = 0L
val intents: MutableMap<Int, Intents> = synchronizedMap(mutableMapOf())

private var _cachedAt = 0L
private const val APPS_CACHE_TIMEOUT = 10 * 60 * 1000L

fun getAllPackages(packageManager: PackageManager): List<App> {
if (_allPackages == null || System.currentTimeMillis() - cachedAt > APPS_CACHE_TIMEOUT) {
if (_allPackages == null || System.currentTimeMillis() - _cachedAt > APPS_CACHE_TIMEOUT) {
update(packageManager)
}

return _allPackages!!
}

fun getVisibleApps(packageManager: PackageManager): List<App> {
if (_visibleApps == null || System.currentTimeMillis() - cachedAt > APPS_CACHE_TIMEOUT) {
if (_visibleApps == null || System.currentTimeMillis() - _cachedAt > APPS_CACHE_TIMEOUT) {
update(packageManager)
}

Expand All @@ -45,7 +49,7 @@ object Cache {
)
}.sortedBy { it.name }

cachedAt = System.currentTimeMillis()
_cachedAt = System.currentTimeMillis()
Logger.d("Cache", "Updated cache")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ interface NotificationDao {
@Query("SELECT * FROM notifications ORDER BY id DESC")
fun list(): Flow<List<Notification>>

@Query("SELECT * FROM notifications ORDER BY timestamp ASC LIMIT :count")
suspend fun listOldestN(count: Int): List<Notification>

@Query("SELECT * FROM notifications WHERE showInHistory = 1 ORDER BY id DESC")
fun history(): Flow<List<Notification>>

Expand All @@ -27,9 +30,6 @@ interface NotificationDao {
@Delete
suspend fun delete(notification: Notification)

@Query("DELETE FROM notifications WHERE id IN (SELECT id FROM notifications ORDER BY timestamp ASC LIMIT :count)")
suspend fun trim(count: Int)

@Query("DELETE FROM notifications")
suspend fun deleteAll()
}
15 changes: 12 additions & 3 deletions app/src/main/java/co/adityarajput/notifilter/data/Repository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ class Repository(
val count = notificationDao.count()
if (count > 50) {
Logger.d("Repository.registerHit", "Deleting oldest ${count - 50} notification(s)")
notificationDao.trim(count - 50)
notificationDao.listOldestN(count - 50).forEach {
Cache.intents.remove(it.data.hashCode())
notificationDao.delete(it)
}
}
}

Expand All @@ -37,9 +40,15 @@ class Repository(

suspend fun delete(filter: Filter) = filterDao.delete(filter)

suspend fun delete(notification: Notification) = notificationDao.delete(notification)
suspend fun delete(notification: Notification) {
Cache.intents.remove(notification.data.hashCode())
notificationDao.delete(notification)
}

suspend fun deleteFilters() = filterDao.deleteAll()

suspend fun deleteNotifications() = notificationDao.deleteAll()
suspend fun deleteNotifications() {
Cache.intents.clear()
notificationDao.deleteAll()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package co.adityarajput.notifilter.data.models

import android.app.ActivityOptions
import android.app.PendingIntent
import android.os.Build
import android.os.Build.VERSION_CODES.BAKLAVA
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.service.notification.StatusBarNotification

data class Intents(
val main: PendingIntent?,
val actions: Map<String, PendingIntent?>,
) {
constructor(sbn: StatusBarNotification) : this(
sbn.notification.contentIntent,
(sbn.notification.actions ?: emptyArray())
.filter { it.remoteInputs == null || it.remoteInputs.isEmpty() }
.associate { it.title.toString() to it.actionIntent },
)

fun launchMain() {
main?.run {
if (Build.VERSION.SDK_INT >= BAKLAVA) {
send(
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
)
.toBundle(),
)
} else if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
@Suppress("DEPRECATION")
send(
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
)
.toBundle(),
)
} else {
send()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,7 @@ data class Notification(
sbn.packageName, sbn.postTime, showInHistory, showInWidget, id,
)

fun isSimilar(other: Notification): Boolean {
return this.origin == other.origin &&
this.title == other.title &&
this.content == other.content &&
this.timestamp == other.timestamp
}
val data get() = listOf(origin, title, content, timestamp)

fun appNameFrom(packages: List<App>) =
packages.find { it.packageName == origin }?.name ?: origin
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
package co.adityarajput.notifilter.services

import android.app.ActivityOptions
import android.app.Notification.FLAG_GROUP_SUMMARY
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.pm.ApplicationInfo
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
import android.media.AudioManager
import android.os.Build
import android.os.Build.VERSION_CODES.BAKLAVA
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM
import android.service.notification.NotificationListenerService
Expand All @@ -17,10 +15,8 @@ import androidx.core.app.NotificationCompat
import co.adityarajput.notifilter.Constants
import co.adityarajput.notifilter.R
import co.adityarajput.notifilter.data.AppContainer
import co.adityarajput.notifilter.data.models.Action
import co.adityarajput.notifilter.data.models.Any
import co.adityarajput.notifilter.data.models.Filter
import co.adityarajput.notifilter.data.models.Notification
import co.adityarajput.notifilter.data.Cache
import co.adityarajput.notifilter.data.models.*
import co.adityarajput.notifilter.utils.Logger
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
Expand Down Expand Up @@ -142,6 +138,7 @@ class NotificationListener : NotificationListenerService() {
"Received $sbn with extras ${sbn.notification.extras}",
)
val notification = Notification(sbn)
val intents = Intents(sbn)
Logger.d("NotificationListener", "Received $notification")

val filter = filters.filter {
Expand Down Expand Up @@ -172,36 +169,17 @@ class NotificationListener : NotificationListenerService() {

is Action.TAP_NOTIFICATION ->
try {
sbn.notification.contentIntent?.run {
if (Build.VERSION.SDK_INT >= BAKLAVA) {
send(
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
)
.toBundle(),
)
} else if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
@Suppress("DEPRECATION")
send(
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED,
)
.toBundle(),
)
} else send()
}
intents.launchMain()
} catch (e: Exception) {
Logger.e("NotificationListener", "Failed to tap notification", e)
return
}

is Action.TAP_BUTTON ->
try {
sbn.notification.actions.find {
Regex(filter.action.buttonRegex).containsMatchIn(it.title)
}?.actionIntent?.send()
intents.actions.entries.find {
Regex(filter.action.buttonRegex).containsMatchIn(it.key)
}?.value?.send()
} catch (e: Exception) {
Logger.e("NotificationListener", "Failed to tap button", e)
return
Expand All @@ -221,7 +199,7 @@ class NotificationListener : NotificationListenerService() {

if ((untilNextBatch < 1000L) || (batchLength - untilNextBatch < 1000L)) {
Logger.d("NotificationListener", "Less than 1 second to batch boundary")
} else if (notifications.any(notification::isSimilar)) {
} else if (notifications.any { it.data == notification.data }) {
Logger.d("NotificationListener", "Already snoozed")
} else {
snoozeNotification(
Expand Down Expand Up @@ -302,6 +280,7 @@ class NotificationListener : NotificationListenerService() {
showInWidget = filter.widgetEnabled,
),
)
Cache.intents[notification.data.hashCode()] = intents
notifications = repository.notifications().first()
Logger.d("NotificationListener", "Notifications updated: $notifications")
}
Expand Down
Loading