diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..10537c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +apktool +apktool.jar +backup/ +extracted/ +services_decompiled/ +verify_patch/ +module/ +*.zip diff --git a/ANALYSIS.md b/ANALYSIS.md new file mode 100644 index 0000000..99641f6 --- /dev/null +++ b/ANALYSIS.md @@ -0,0 +1,80 @@ +# Viwoods Notification Filter Analysis - Firmware 1.3.8 + +## Location: NotificationManagerService.smali (Lines 6407-6480) + +### Notification Suppression Logic Found + +The Viwoods notification blocking mechanism is located in a method that handles notification posting. This is the critical filtering point: + +```smali +.line 9157 +:cond_17 +invoke-virtual {p0, p2}, Lcom/android/server/notification/NotificationManagerService;->areNotificationsEnabledForPackageInt(I)Z +move-result p1 # p1 = areNotificationsEnabledForPackageInt(p2) +xor-int/2addr p1, v3 # p1 = p1 XOR v3 (INVERTS the result!) +``` + +**Key Finding:** +- `v3` is set to 1 earlier in the method +- The XOR inverts the boolean result from `areNotificationsEnabledForPackageInt` +- If notifications ARE enabled → p1 becomes 0 (false) +- If notifications ARE DISABLED → p1 becomes 1 (true) + +### Flow After Permission Check + +```smali +.line 9158 +iget-object p3, p0, Lcom/android/server/notification/NotificationManagerService;->mNotificationLock:Ljava/lang/Object; +monitor-enter p3 +.line 9159 +:try_start_2 +invoke-virtual {p0, p5}, Lcom/android/server/notification/NotificationManagerService;->isRecordBlockedLocked(Lcom/android/server/notification/NotificationRecord;)Z +move-result p4 +or-int/2addr p1, p4 # p1 = p1 OR isRecordBlockedLocked(p5) +.line 9160 +monitor-exit p3 +``` + +### Suppression Decision + +```smali +if-eqz p1, :cond_19 # If p1 == 0 (no suppression needed), skip to cond_19 +.line 9161 +invoke-virtual {v0}, Landroid/app/Notification;->isMediaNotification()Z +move-result p1 +if-nez p1, :cond_19 # Skip if it's a media notification +invoke-virtual {p0, v1, p2, v0}, Lcom/android/server/notification/NotificationManagerService;->isCallNotification(Ljava/lang/String;ILandroid/app/Notification;)Z +move-result p1 +if-nez p1, :cond_19 # Skip if it's a call notification +.line 9162-9480 +[SUPPRESS NOTIFICATION - log "Suppressing notification from package..."] +``` + +## Patch Strategy + +To disable the notification whitelist filter, we need to: + +1. **Option A:** Make `p1` always 0 after line 6413 + - This would allow all notifications through regardless of whitelist status + +2. **Option B:** Remove the XOR inversion at line 6413 + - Change `xor-int/2addr p1, v3` to do nothing + +3. **Option C:** Skip the permission check entirely + - Jump directly to cond_19 (allow notification) + +**Recommended:** Option A - Setting `p1` to 0 immediately after the permission check is the cleanest approach. This makes the conditional at line 6433 always false, bypassing suppression. + +## Implementation + +Replace lines 6413 after the areNotificationsEnabledForPackageInt call: +``` +xor-int/2addr p1, v3 # ORIGINAL: inverts boolean +``` + +With: +``` +const/4 p1, 0x0 # NEW: set p1 to always 0 (allow notification) +``` + +This ensures `if-eqz p1, :cond_19` at line 6433 is always true, skipping the suppression logic. diff --git a/CONTEXT.md b/CONTEXT.md new file mode 100644 index 0000000..d049b10 --- /dev/null +++ b/CONTEXT.md @@ -0,0 +1,158 @@ +# Viwoods AiPaper — Notification Unlocker Project Context + +## Device + +**Viwoods AiPaper** — an Android-based e-ink reader tablet. +- Firmware: **1.3.8** +- Rooted with **Magisk** (bootloader unlocked, `init_boot.img` patched) +- Device boots normally with the module installed, no bootloop issues + +--- + +## Problem + +Viwoods ships firmware with a **hardcoded notification whitelist**. Only apps explicitly approved by Viwoods can post notifications. All other apps (WhatsApp, Gmail, third-party apps) are silently blocked. The blocking is done inside `services.jar` — the core Android framework services file — making it invisible to standard Android notification settings. + +--- + +## Investigation + +The patching target is `/system/framework/services.jar`, specifically `classes2.dex` inside it, class `com.android.server.notification.NotificationManagerService`. + +Three separate filtering points were found in firmware 1.3.8 (more than in prior firmware versions 1.1.0 / 1.2.3): + +### Filter 1 — `checkDisqualifyingFeatures()` +**File:** `NotificationManagerService.smali` ~line 6413 + +The method calls `areNotificationsEnabledForPackageInt(uid)` → `PermissionHelper.hasPermission(uid)` → `checkPermission("android.permission.POST_NOTIFICATIONS", uid)`. Viwoods only grants `POST_NOTIFICATIONS` to whitelisted apps. + +The result was XOR'd with `v3=1`, inverting it, then fed into a suppression check. This was a Viwoods-specific bug on top of the whitelist — even whitelisted apps could be affected under certain conditions. + +**Patch:** replaced `xor-int/2addr p1, v3` with `const/4 p1, 0x0` — forces the result to always bypass suppression. + +### Filter 2 — `PostNotificationRunnable.postNotification()` +**File:** `NotificationManagerService$PostNotificationRunnable.smali` ~line 188 + +This is the **actual active code path** for posting notifications in firmware 1.3.8. `EnqueueNotificationRunnable` creates a `PostNotificationRunnable` and posts it to the handler. This runnable independently checks `areNotificationsEnabledForPackageInt(uid)` at line 184 and stores the result in `v0`. At line 362, `if-eqz v0, :cond_4` blocks the notification if the app is not whitelisted — no XOR bug here, clean logic, but still enforcing the whitelist. + +**Patch:** added `const/4 v0, 0x1` after `move-result v0` at line 186 — forces the permission result to always be "granted", bypassing the whitelist. Standard Android per-app notification toggles (`isRecordBlockedLocked`) are preserved so users can still disable individual apps in Settings. + +### Filter 3 — `enqueueNotificationInternal()` ALLOWED_PKGS check +**File:** `NotificationManagerService.smali` ~line 9360 + +At the very entry point of `enqueueNotificationInternal()`, before any other processing, the firmware checks the package name against a static `ALLOWED_PKGS` set. If the package is not in the set, it logs `"eink project,Blocked notification from package: "` and returns false immediately. Only `com.google.android.gms` and `com.google.android.apps.wellbeing` are explicitly hardcoded as allowed outside the set. + +This was the log message visible in logcat: `I NotificationService: eink project,Blocked notification from package: com.whatsapp` + +**Patch:** replaced `if-nez v2, :cond_2` with `goto :cond_2` — bypasses the `ALLOWED_PKGS` check entirely, making all packages proceed to normal notification processing. + +--- + +## How the Patch is Applied + +The workflow uses **apktool** to decompile and recompile `services.jar`: + +```powershell +# Decompile (already done — result is in services_decompiled/) +java -jar apktool.jar d backup/services.jar.original -o services_decompiled + +# After editing smali files, recompile +java -jar apktool.jar b services_decompiled -o extracted/services_patched.jar + +# Copy into module +cp extracted/services_patched.jar module/services.jar + +# Repackage ZIP for Magisk +Compress-Archive -Path "module\*" -DestinationPath "Viwoods-Notification-Unlocker-1.3.8.zip" +``` + +The `post-fs-data.sh` script in the module copies `services.jar` from the module root to `$MODDIR/system/framework/services.jar` at boot, sets the SELinux context (`chcon u:object_r:system_file:s0`), and permissions (644). Magisk then overlays this over `/system/framework/services.jar` via its magic mount. + +--- + +## Battery Optimization Fix + +After unlocking notifications, a secondary problem emerged: **Viwoods firmware kills background app processes aggressively**. WhatsApp's process was being killed before it could receive and process FCM push messages (`result=CANCELLED` in GCM logcat). The fix is `service.sh`, which runs after every boot: + +```sh +until [ "$(getprop sys.boot_completed)" = "1" ]; do sleep 2; done + +pm list packages | cut -d: -f2 | while read -r pkg; do + dumpsys deviceidle whitelist +"$pkg" 2>/dev/null + cmd appops set "$pkg" RUN_ANY_IN_BACKGROUND allow 2>/dev/null +done +``` + +This whitelists all 241 installed packages (system + user) from battery optimization. `pm list packages` (without `-3`) is required because system-preloaded apps like Gmail (`com.google.android.gm`) are not listed with `-3` (user-only flag). + +**Limitation:** apps installed after the last reboot are not covered until the next reboot. + +--- + +## Module Structure + +``` +module/ +├── META-INF/com/google/android/ +│ ├── update-binary (minimal Magisk installer) +│ └── updater-script (empty, required by format) +├── module.prop (id, name, version, author, description) +├── post-fs-data.sh (copies + SELinux context for services.jar) +├── service.sh (battery optimization whitelist at boot) +└── services.jar (patched framework — 22MB) +``` + +Root-level files (`module.prop`, `post-fs-data.sh`, `service.sh`, `services.jar`) match the upstream repo's flat structure. + +--- + +## Repository + +- **Upstream:** https://github.com/ScreenSensitive/Viwoods-Notification-Unlocker +- **Fork:** https://github.com/magcrider/Viwoods-Notification-Unlocker +- **PR:** https://github.com/ScreenSensitive/Viwoods-Notification-Unlocker/pull/2 +- **Branch:** `firmware/1.3.8` (merged into fork's `main`) +- **Working directory:** `c:\Users\Harvey Botero\Desktop\GIT\viwoods` + +The upstream repo previously supported firmware 1.1.0 and 1.2.3. Firmware 1.3.8 introduced the additional `PostNotificationRunnable` and `ALLOWED_PKGS` filters not present in earlier versions. + +--- + +## Launcher + +The stock Viwoods launcher has no notification drawer (swipe down only refreshes the e-ink screen). The user settled on **inkOS** — https://github.com/gezimos/inkOS — a minimalist e-ink-friendly launcher with a notification tray. It is already referenced in the upstream README as the recommended companion. + +--- + +## Outstanding Items + +### Gmail notifications +Gmail notifications were not working after all other fixes. Diagnosis showed: +- Battery optimization: resolved (service.sh covers Gmail) +- GMS (Google Play Services) is running and receiving FCM +- Gmail's email notification channels (`Primary`, `Promotions`, etc.) were not registered in the notification system — only Drive-related channels (COMMENTS, SHARES) were present +- Gmail sync was set to `period=1d00h00m00s` (once daily) instead of FCM push + +**Most likely cause:** Gmail's in-app notification setting is set to "None". +**Next step to try:** Gmail app → Menu → Settings → [account] → Notifications → set to "All new mail". +This is separate from the module and not part of the PR. + +### Fork main merge +Completed — `firmware/1.3.8` has been merged into `magcrider/main`. + +### Upstream PR +Open and awaiting review by ScreenSensitive. No CI checks configured on the upstream repo. + +--- + +## Files NOT committed (gitignore) + +| Path | Reason | +|------|--------| +| `apktool.jar` / `apktool` | Build tools, 24MB | +| `backup/` | Original unpatched services.jar (22MB) | +| `extracted/` | Build output | +| `services_decompiled/` | Decompiled smali source (229MB) | +| `verify_patch/` | Secondary decompile for verification (207MB) | +| `*.zip` | Release artifacts, not source | +| `module/` | Files already at root level | diff --git a/INSTALLATION.md b/INSTALLATION.md new file mode 100644 index 0000000..1794a37 --- /dev/null +++ b/INSTALLATION.md @@ -0,0 +1,137 @@ +# Viwoods Notification Unlocker v1.3.8 - Installation Guide + +## Module Files +- **Viwoods-Notification-Unlocker-1.3.8.zip** - Flashable Magisk module (9.2MB) + +## Installation Steps + +### Via Magisk App (Recommended) + +1. **Transfer the ZIP to your device** (USB, ADB, or file transfer) + ```bash + adb push Viwoods-Notification-Unlocker-1.3.8.zip /sdcard/Download/ + ``` + +2. **Open Magisk Manager app** on your Viwoods device + +3. **Navigate to "Modules"** section + +4. **Tap the "+" or "Install from storage" button** + +5. **Select the ZIP file:** + ``` + Viwoods-Notification-Unlocker-1.3.8.zip + ``` + +6. **Wait for installation to complete** (usually 10-30 seconds) + +7. **Reboot your device** (tap "Reboot" in Magisk app or power off/on) + +### Via ADB Manual Installation + +```bash +# Push the ZIP to device +adb push Viwoods-Notification-Unlocker-1.3.8.zip /data/adb/modules/ + +# Or extract and push directly (advanced) +adb push module /data/adb/modules/viwoods_notification_unlocker_1.3.8/ +``` + +## What This Module Does + +- **Patches:** `/system/framework/services.jar` (NotificationManagerService) +- **Effect:** Disables the Viwoods manufacturer notification whitelist +- **Result:** All apps can now send notifications, even if blocked by Viwoods + +## Testing the Module + +### After Installation + +1. **Verify system boot:** + - Device should boot normally (no bootloop) + - system_server should not crash + - No Safe Mode trigger + +2. **Check Magisk app:** + - The module should appear in "Modules" list as **active** + - Status should show ✓ (checkmark) + +3. **Test notifications:** + - Install a test app (e.g., a messaging app blocked by Viwoods) + - Send a test notification + - **Expected:** Notification appears on lock screen/notification bar + - **Previous behavior:** Notification was silently dropped + +### Logcat Verification + +```bash +adb logcat | grep -i notification +``` + +Look for lines like: +``` +NotificationService: Suppressing notification... (SHOULD NOT APPEAR AFTER PATCH) +NotificationManagerService: Notification posted (SHOULD APPEAR WITH PATCH) +``` + +## Troubleshooting + +### Module causes bootloop +- The system will automatically enter **Magisk Safe Mode** (hold Volume Down at boot) +- Magisk will disable the problematic module +- No need to re-flash or factory reset + +### Module doesn't appear in Magisk app +- Ensure ZIP is properly extracted +- Check that all files are in place (module.prop, services.jar, post-fs-data.sh) +- Try re-flashing the ZIP + +### Notifications still blocked after installation +- Verify module is **active** in Magisk app (shown with ✓) +- Try rebooting the device again +- Check if the app has notification permissions in Settings +- Run `adb logcat` and look for suppression messages + +## Rollback/Uninstall + +1. Open **Magisk app** +2. Go to **Modules** +3. Find **"Viwoods Notification Unlocker"** +4. Tap the **delete/trash icon** +5. **Reboot** when prompted +6. Notifications will return to normal (whitelist re-enabled) + +## File Integrity + +### Module Contents +``` +module/ +├── META-INF/ +│ └── com/google/android/ +│ ├── update-binary +│ └── updater-script +├── module.prop (Module metadata) +├── post-fs-data.sh (Installation script) +└── services.jar (Patched framework for 1.3.8) +``` + +### Verify Patch +```bash +# Check that patch was applied to services.jar +# File size should be exactly 22M (23452288 bytes) +# MD5 should be: 9e51323354cf6f8467747fa7de850c96 +adb shell md5sum /system/framework/services.jar +``` + +## Support & Reporting Issues + +- **Device:** Viwoods E-Ink Reader +- **Firmware:** 1.3.8 +- **Original Module:** https://github.com/ScreenSensitive/Viwoods-Notification-Unlocker +- **XDA Thread:** https://xdaforums.com/t/guide-unlock-bootloader-root-viwoods-reader-with-magisk.4772639/ + +If issues occur, document: +1. Bootloop (yes/no) +2. Logcat output during issue +3. Which apps still have blocked notifications +4. Device model and Magisk version diff --git a/README.md b/README.md index a9c31a1..4f00f3c 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,16 @@ If this module saved you time or frustration, consider supporting development: ### ✅ Tested * **Device:** Viwoods Reader -* **Firmware:** Viwoods software **1.1.0** and **1.2.3** +* **Firmware:** Viwoods software **1.1.0**, **1.2.3**, and **1.3.8** + +> Use the release matching your firmware version — each version patches different code offsets. ### ⚠️ Untested * Other Viwoods devices * Other firmware versions -> **Important:** This module is verified **only on Viwoods firmware 1.1.0 and 1.2.3**. +> **Important:** This module is verified **only on Viwoods firmware 1.1.0, 1.2.3, and 1.3.8**. > Other versions may have different code paths or offsets. --- @@ -53,7 +55,16 @@ The check lives in `services.jar` (specifically `classes2.dex`) and drops notifi * **File:** `/system/framework/services.jar` * **DEX:** `classes2.dex` * **Class:** `com.android.server.notification.NotificationManagerService` -* **Method:** `enqueueNotificationInternal()` + +Firmware **1.3.8** introduced two additional filter points compared to earlier versions: + +| Method | Filter | +|--------|--------| +| `enqueueNotificationInternal()` | `ALLOWED_PKGS` hardcoded package whitelist | +| `checkDisqualifyingFeatures()` | XOR-inverted `areNotificationsEnabledForPackageInt()` result | +| `PostNotificationRunnable.postNotification()` | `POST_NOTIFICATIONS` permission check against the whitelist | + +All three are patched in the 1.3.8 release. --- @@ -78,7 +89,7 @@ This Magisk module **patches `classes2.dex` inside `services.jar` systemlessly** * **Root access via Magisk** * Root **requires patching `init_boot.img`** using the Magisk app -* Viwoods firmware **1.1.0 or 1.2.3** +* Viwoods firmware **1.1.0**, **1.2.3**, or **1.3.8** ### Steps @@ -108,7 +119,7 @@ Since the Viwoods Reader **lacks a native pull-down notification tray**, it is * ## ⚠️ Warnings & Notes -* Designed specifically for the **Viwoods Reader firmware 1.1.0/1.2.3** +* Designed specifically for the **Viwoods Reader firmware 1.1.0, 1.2.3, and 1.3.8** * OTA updates may overwrite or invalidate the patch * Low-level system service modification — proceed at your own risk diff --git a/module.prop b/module.prop index 6b4f841..ecaf01c 100644 --- a/module.prop +++ b/module.prop @@ -1,6 +1,6 @@ -id=eink_notification_unblock -name=E-ink Notification Unblock v2 -version=2.0 -versionCode=2 -author=J -description=Removes notification blocking with proper SELinux context. Allows ALL apps to show notifications on e-ink devices. +id=viwoods_notification_unlocker +name=Viwoods Notification Unlocker +version=v1.3.8 +versionCode=138 +author=ScreenSensitive +description=Disables the notification whitelist check on Viwoods E-Ink Reader (Firmware 1.3.8) diff --git a/post-fs-data.sh b/post-fs-data.sh index e2008aa..e16f19f 100644 --- a/post-fs-data.sh +++ b/post-fs-data.sh @@ -1,10 +1,16 @@ #!/system/bin/sh -# E-ink Notification Unblock - SELinux Context Fixer -# This ensures the patched services.jar has the correct SELinux context +# Viwoods Notification Unlocker - Installation Script +# This script patches the NotificationManagerService to disable the notification whitelist MODDIR=${0%/*} -# Set proper SELinux context for services.jar +# Ensure module directory structure exists +mkdir -p "$MODDIR/system/framework" + +# Copy patched services.jar to the system framework directory +cp "$MODDIR/services.jar" "$MODDIR/system/framework/services.jar" 2>/dev/null + +# Set proper SELinux context chcon u:object_r:system_file:s0 "$MODDIR/system/framework/services.jar" 2>/dev/null # Set proper permissions diff --git a/service.sh b/service.sh new file mode 100644 index 0000000..b329091 --- /dev/null +++ b/service.sh @@ -0,0 +1,15 @@ +#!/system/bin/sh +# Viwoods Notification Unlocker - Boot service +# Disables battery optimization for all user-installed apps so they +# can receive notifications without being killed in the background + +# Wait for system to be fully booted +until [ "$(getprop sys.boot_completed)" = "1" ]; do + sleep 2 +done + +# Apply to every user-installed app (covers future installs too) +pm list packages | cut -d: -f2 | while read -r pkg; do + dumpsys deviceidle whitelist +"$pkg" 2>/dev/null + cmd appops set "$pkg" RUN_ANY_IN_BACKGROUND allow 2>/dev/null +done diff --git a/services.jar b/services.jar index cbed8da..f98c335 100644 Binary files a/services.jar and b/services.jar differ