# X4.24 — Android Print Agent MVP — Implementation Report

Date: 2026-10-07

## A. Audit

Kontrak existing diverifikasi langsung dari source (lihat
`reports/X4.24_ANDROID_CONTRACT_AUDIT.md`): Device API (Bearer token,
claim 3s/10s/backoff, claim_token per generasi, lease 90s, ACK
SUCCESS/FAILURE + idempotent + STALE_CLAIM, rate limit 1s), payload plain
text UTF-8, destination/branch dari registrasi device. **Kesimpulan:
Android dapat memakai API existing tanpa perubahan server apa pun.**

## B. Android implementation

Lokasi: `android-print-agent/` (direktori baru — tidak menyentuh satu pun
file existing repo).

| File | Isi |
|---|---|
| `src/agent/core/EscPosRenderer.java` | teks → ESC/POS (init, wrap per CPL, center header, cut opsional GS V 66 0, CP437 default + UTF-8 passthrough) |
| `src/agent/core/Cp437.java` | mapping CP437 (Rp, ·, karakter Indonesia; unmappable → '?') |
| `src/agent/core/Json.java` | mini JSON codec (tanpa dependency) |
| `src/agent/core/DeviceApiProtocol.java` | builder/parser claim + ACK (semua status HTTP, malformed, http=0) |
| `src/agent/core/AgentPolicy.java` | policy bridge-parity (idle 3s, timeout 10s, backoff ×2 cap 30s, ACK retry 3×, threshold transport 6×) |
| `src/agent/core/PrintPipeline.java` | siklus claim→print→ACK; lease-aware; TANPA re-print lokal saat ACK timeout; TANPA re-claim saat claim timeout |
| `src/agent/android/MainActivity.java` | setup only (server URL, token, printer ter-pairing, TEST CONNECTION via /heartbeat, TEST PRINT lokal tanpa print_jobs, START/STOP) |
| `src/agent/android/PrintAgentService.java` | foreground service (loop pipeline, notifikasi state, HttpTransport HTTPS-only) |
| `src/agent/android/BluetoothPrinter.java` | SPP RFCOMM (paired only, connect/write/flush/reconnect) |
| `src/agent/android/SecureTokenStore.java` | token di Android Keystore AES-GCM; ciphertext di prefs; tak pernah plaintext |
| `src/agent/android/BootReceiver.java` | autostart setelah reboot bila konfigurasi lengkap |
| `src/main/AndroidManifest.xml`, `build.gradle` | minSdk 29 (Android 10+), zero third-party dependencies |
| `README.md` | build, operasional, switchover, batasan |

**Deviasi keputusan (disclosed):** Java, bukan Kotlin — environment
repository hanya punya JDK 11 (tanpa Gradle/kotlinc), sehingga core
pure-JVM dapat diuji EKSEKUSI hari ini; agent tetap zero-dependency
(konsisten filosofi repo). Renderer/protokol/pipeline 100% platform-netral
(compile + jalan di JVM tanpa android.jar).

## C. Automated tests — **48/48 PASS (dieksekusi, bukan sekadar ditulis)**

```
javac -encoding UTF-8 -d out src/agent/core/*.java test/*.java
java -cp out agent.test.RunTests
→ PASSED: 48  FAILED: 0
```

Cakupan: renderer (14: wrap/word-boundary/empty/CRLF/CP437/·/UTF-8/cut/
center/50 baris), JSON (7), protokol claim/ACK (13), policy (4), pipeline
(10: idempotensi ACK-timeout **terbukti tidak mencetak ulang**, claim-
timeout tidak re-claim, print-gagal → ACK FAILURE, STALE_CLAIM berhenti,
threshold 6×, lease expired tidak dicetak).

## D. Hardware validation — **NOT TESTED (10/10)**

Tidak ada tablet Android terhubung (`adb devices` kosong) dan tidak ada
printer Bluetooth SPP ter-pairing di mesin dev (hanya device BLE).
H1–H10 **TIDAK dieksekusi** — tidak boleh dipalsukan. Matriks H1–H10
terlampir di README + laporan ini untuk dijalankan operator saat hardware
tersedia.

## E. Security

- Token: Android Keystore AES-GCM, ciphertext-only di prefs; tidak pernah
  di-log (log hanya event/status/job/http/duration/error), tidak di UI,
  tidak di source, tidak di commit.
- HTTPS wajib: transport menolak URL non-https; validasi sertifikat ON.
- Branch/destination: server-authoritative (client tidak mengirimnya).
- 401 → agent berhenti (intervensi operator) — parity bridge.

## F. Reliability (dibuktikan tes JVM)

- Network loss: http=0 → backoff, threshold 6× → keluar terkontrol; job
  persist server.
- Bluetooth loss: print gagal → ACK FAILURE (bukan SUCCESS palsu) → job
  re-claimable ≤3 attempt.
- ACK timeout: retry 3× claim_token SAMA; tidak pernah reprint lokal;
  lease server 90s menjadi otoritas (at-least-once, didokumentasikan).
- Service restart/reboot: START_STICKY + BootReceiver; stale claim
  dipulihkan lease.
- Claim timeout: tidak re-claim langsung (server state tak diketahui).

## G. Server impact

**SERVER CHANGE: NONE.** Tidak ada endpoint baru, tidak ada perubahan
kontrak, tidak ada migration, tidak ada perubahan Windows bridge.

## H. Acceptance criteria vs fakta

| Kriteria | Status |
|---|---|
| Android hanya printer adapter; server authoritative | PASS |
| Protokol + token model existing dipakai ulang | PASS |
| Tanpa modifikasi server | PASS |
| Token terproteksi, tak di-log | PASS |
| Network/Bluetooth failure tidak fake-ACK | PASS (tes pipeline) |
| ACK timeout tidak reprint lokal | PASS (tes pipeline) |
| Service recoverable | PASS (desain + START_STICKY/BootReceiver) |
| Automated tests PASS | PASS (48/48 eksekusi) |
| Real printer test PASS | **NOT TESTED** (tanpa hardware) |

---

```
========================================
X4.24 STATUS
========================================

CONDITIONAL PASS

SERVER CHANGE: NONE
AUTOMATED TESTS: 48/48 (JVM, dieksekusi)
HARDWARE TESTS: 0/10 (NOT TESTED — tanpa tablet Android + printer SPP)
REAL PRINT: NOT TESTED
DUPLICATE RISK: at-least-once (didokumentasikan; jendela ACK-timeout total + lease expiry — identik Windows bridge)
TOKEN SECURITY: PASS (Keystore AES-GCM, token-safe logging, HTTPS-only)
NEXT RECOMMENDED PHASE: X4.25 — HARDWARE VALIDATION (build APK di mesin
dengan Android SDK + Gradle, jalankan H1–H10 di outlet dengan printer SPP
dan tablet dapur; switchover device Windows↔Android per prosedur)
```

**Blocker kosmetik vs riil:** tidak ada blocker kode; yang belum
terbukti hanya build-APK (butuh SDK/Gradle di mesin build) dan
validasi hardware fisik — keduanya membutuhkan environment/outlet, bukan
perubahan desain.
