Skip to content
Open
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: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apktool
apktool.jar
backup/
extracted/
services_decompiled/
verify_patch/
module/
*.zip
80 changes: 80 additions & 0 deletions ANALYSIS.md
Original file line number Diff line number Diff line change
@@ -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.
158 changes: 158 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -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: <pkg>"` 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 |
137 changes: 137 additions & 0 deletions INSTALLATION.md
Original file line number Diff line number Diff line change
@@ -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
Loading