Prepare public project for distribution

This commit is contained in:
ajp_anton
2026-07-25 04:09:04 +00:00
parent 4d99c199dd
commit 48e61e3be7
3 changed files with 80 additions and 48 deletions
+3
View File
@@ -2,6 +2,9 @@
/project.md /project.md
/local/ /local/
/local.properties /local.properties
*.jks
*.keystore
keystore.properties
# Gradle and Android build output # Gradle and Android build output
/.gradle/ /.gradle/
+28 -46
View File
@@ -1,61 +1,43 @@
# Notification Log # Notification Log
Notification Log is a private, non-root Android notification listener for API Notification Log keeps a private history of the notifications that appear on
3436. It keeps an encrypted local history of notification appearances, edits, your Android device. It works as a standard Android notification listener; it
and removal reasons, including a distinction between an app cancelling its own does not need root or an Xposed module.
notification and a user dismissing it.
## Privacy and capture limits ## What it records
The event log and copied image files are encrypted at rest with an Android - Notifications appearing, changing, and disappearing.
Keystore-backed AES-GCM key. Export is deliberately unencrypted only after the - Whether a notification was dismissed by you or cancelled by its app.
user acknowledges a warning and chooses a destination through Android's Storage - Available text, messaging-style content, and readable notification images.
Access Framework. - Common removal reasons such as timeouts and channel changes.
Android exposes standard notification extras such as title, text, big text, You can choose which event types to record, set app-specific allow/block lists,
inbox lines, messaging-style messages, and readable big-picture content. This and suppress routine progress or timer updates. The log view can be tailored,
app copies a readable bitmap at post time when Android provides one. It cannot expanded for long entries, and exported as CSV, aligned text, or an HTML ZIP.
reconstruct opaque custom `RemoteViews`, OTP content Android redacts, or image
URIs/Icons which Android does not allow it to load. Such images remain marked
as `[image]` without causing the text event to be lost.
The app declares `QUERY_ALL_PACKAGES` solely to implement the settings pages' ## Privacy
“all installed apps” list. A Play-distributed build must meet Google Play's
restricted package-visibility policy and provide the required declaration; a
future distribution variant may need a narrower app-selection flow.
## Background behavior Logs and retained notification images are encrypted on the device with an
Android Keystore key. Exporting is an explicit action and produces an
unencrypted file at the location you choose. Android may withhold sensitive or
custom notification content; this app records only what Android makes available
to a notification listener.
Android owns the notification-listener connection; this app has no polling, When all logging types are disabled, the notification listener is disabled too:
scheduled job, alarm, or boot receiver. When one or more logging types are the app has no polling, scheduled job, or background service to run.
enabled and notification access has been granted, Android binds the listener
again after device boot. When every logging type is disabled, the app disables
that listener component: it has no background service and does not start at
boot. Re-enabling any logging type re-enables and asks Android to rebind it.
## Notification update policy ## Requirements
Each platform notification key is tracked independently. Group summaries are Notification Log supports Android 14 through Android 16 (API levels 3436).
logged as their own platform notifications; the app does not invent extra group After installation, grant **Notification access** when prompted. The optional
events. Ongoing notifications are recorded when Android posts, updates, or app lock uses your device's biometric or credential prompt.
removes them, but are never treated as user-dismissible. Identical reposts are
ignored. With the default **Ignore routine updates** setting, standard
progress/chronometer notifications do not create edit rows; non-routine content
changes do. Disabling that setting records the updates as edits.
## Development checks ## Development
Build a debug APK with:
```bash ```bash
./gradlew test assembleDebug ./gradlew assembleDebug
./scripts/run-aosp-api36-notification-smoke.sh
``` ```
The AOSP API 36 smoke scenario starts from a clean emulator snapshot, grants This project has been developed with substantial assistance from AI tools.
listener access, drives all helper notification variants, verifies encrypted
event and image persistence, and verifies that the listener records another
event after an emulator reboot. It deliberately leaves user swipe dismissal,
biometric lock behavior, and OEM/foldable rendering to device testing.
The local Android test lab has been checked on AOSP API 34/35/36, Google APIs
API 35/36, and LineageOS API 35/36 using clean LSPosed snapshots. Root and
Xposed are not used by this application.
+49 -2
View File
@@ -1,3 +1,5 @@
import java.io.File
import java.util.Properties
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
@@ -5,6 +7,32 @@ plugins {
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
} }
val releaseSigningProperties = Properties()
val releaseSigningPropertiesFile = File(
System.getProperty("user.home"),
".config/android/release-signing.properties",
)
if (releaseSigningPropertiesFile.isFile) {
releaseSigningPropertiesFile.inputStream().use(releaseSigningProperties::load)
}
val releaseKeystorePath = releaseSigningProperties.getProperty("storeFile")
val releaseKeyAlias = releaseSigningProperties.getProperty("keyAlias")
val releaseStorePassword = providers.gradleProperty("androidReleaseStorePassword").orNull
val releaseKeyPassword = providers.gradleProperty("androidReleaseKeyPassword").orNull
val hasReleaseSigning = !releaseKeystorePath.isNullOrBlank() &&
!releaseKeyAlias.isNullOrBlank() &&
!releaseStorePassword.isNullOrBlank() &&
!releaseKeyPassword.isNullOrBlank()
val releaseBuildRequested = gradle.startParameter.taskNames.any { it.contains("release", ignoreCase = true) }
if (releaseBuildRequested && !hasReleaseSigning) {
error(
"Release signing is not configured. Configure storeFile and keyAlias in " +
"~/.config/android/release-signing.properties and passwords in ~/.gradle/gradle.properties.",
)
}
android { android {
namespace = "se.ajpanton.notificationlog" namespace = "se.ajpanton.notificationlog"
compileSdk = 36 compileSdk = 36
@@ -13,8 +41,8 @@ android {
applicationId = "se.ajpanton.notificationlog" applicationId = "se.ajpanton.notificationlog"
minSdk = 34 minSdk = 34
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = project.findProperty("notificationLogVersionCode")?.toString()?.toIntOrNull() ?: 1
versionName = "0.1.0" versionName = project.findProperty("notificationLogVersionName")?.toString() ?: "0.1"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -23,6 +51,25 @@ android {
viewBinding = true viewBinding = true
} }
signingConfigs {
if (hasReleaseSigning) {
create("release") {
storeFile = file(releaseKeystorePath!!)
storePassword = releaseStorePassword
keyAlias = releaseKeyAlias
keyPassword = releaseKeyPassword
}
}
}
buildTypes {
getByName("release") {
if (hasReleaseSigning) {
signingConfig = signingConfigs.getByName("release")
}
}
}
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17