Prepare public project for distribution
This commit is contained in:
@@ -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/
|
||||||
|
|||||||
@@ -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
|
||||||
34–36. 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 34–36).
|
||||||
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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user