# Android Production Audit — Session Reference

## UNSPOOLED audit (2026-05-25)

### Code quality scan results

| Pattern | Matches | Action |
|---------|---------|--------|
| `!!.` force-unwrap | 0 | None — excellent |
| `println` / `System.out` | 1 (CrashLogger only) | Acceptable — uses PrintWriter |
| `GlobalScope` | 1 (CrashLogger) | **FIXED** — replaced with `CoroutineScope(SupervisorJob() + IO)` |
| `TODO` / `FIXME` | 2 | Minor — profile detail screen stubs |
| Secrets in `Log.*` | 0 | None — excellent |

### ProGuard gaps found

The original `proguard-rules.pro` only had rules for:
- kotlinx.serialization
- Retrofit + OkHttp
- Room
- Hilt
- Coil

**Missing (all added):**
- Media3 / ExoPlayer — reflection for renderer discovery, codec enumeration
- Jetpack Compose — synthetic methods from compose compiler
- Gson — TypeAdapter/TypeAdapterFactory/JsonSerializer/JsonDeserializer
- NanoHTTPD — class loading in local server
- Kotlin coroutines — MainDispatcherFactory, CoroutineExceptionHandler
- ViewModels — @HiltViewModel constructors

### Memory fix: GlobalScope → dedicated scope

**Before (dangerous):**
```kotlin
GlobalScope.launch(Dispatchers.IO) {
    rotateIfNeeded()
    logFile.appendText(line)
}
```

**After (safe):**
```kotlin
private val writeScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

writeScope.launch {
    rotateIfNeeded()
    logFile.appendText(line)
}
```

Why: `GlobalScope` is detached from lifecycle. If the app process dies mid-write, the coroutine is silently abandoned. A dedicated scope with `SupervisorJob()` ensures one failed write doesn't cancel the entire scope, while `Dispatchers.IO` keeps disk I/O off the main thread.

### Security posture (UNSPOOLED)

| Layer | Configuration | Verdict |
|-------|-------------|---------|
| Network | HTTPS-by-default; cleartext only for localhost + debrid OAuth | ✅ |
| Permissions | INTERNET + ACCESS_NETWORK_STATE only | ✅ |
| Backup | allowBackup=false, fullBackupContent=false | ✅ |
| Token storage | Keystore-backed AES-256/GCM (SecureTokenStore) | ✅ |
| API keys | BuildConfig from gradle.properties/env | ✅ |
| Secrets in logs | 0 instances | ✅ |

### Branding fix

NexSidebar showed "NEXSTREAM" (package name) — changed to "UNSPOOLED" / "U" to match app name in manifest, intro splash, and onboarding.

### Docs cleanup

Removed 17 files from `docs/archive/` (136KB total):
- Session handoff notes (2)
- Architecture audits (2)
- Research documents (3)
- Troubleshooting logs (2)
- QA checklists (2)
- UI reference audits (2)
- Refactor ledgers (1)
- Claude Code handoffs (1)
- Compliance checklists (1)
- Security audits (1)

Kept: `PROJECT_STATE.md` (root) + `docs/ARCHITECTURE_GUIDE.md`

### Final metrics

| Metric | Before | After |
|--------|--------|-------|
| GlobalScope instances | 1 | 0 |
| Stale docs | 17 files | 2 files |
| ProGuard missing subsystems | 4 | 0 |
| Branding inconsistencies | 1 | 0 |
| !! force-unwraps | 0 | 0 |
| Secrets in logs | 0 | 0 |
| Tests | 128/128 | 128/128 |
