An Android app that runs on warehouse tablets and exposes a local HTTP API for printing AWB shipping labels on a GAINSCHA GS-2406T label printer over WiFi.
Fully Kiosk Browser (running the Sirdab warehouse webapp) calls http://localhost:8080/print with a PDF URL — the app downloads the PDF, renders it, and sends it to the printer via the GAINSCHA SDK.
Fully Kiosk Browser Companion App (localhost:8080) Printer
───────────────────── ────────────────────────────── ────────
POST /print {pdfUrl} ──▶ Download PDF
Render to bitmap (203 DPI)
Connect via TCP (WiFi) ──▶ Print label
◀────────────── { ok: true, pages: 1 }
The app runs as a foreground service, auto-starts on boot, and self-heals if the HTTP server or printer connection drops.
- Android 5.0+ (API 21), tablet with WiFi
- GAINSCHA GS-2406T label printer on the same WiFi network
- Fully Kiosk Browser (kiosk mode) for the warehouse webapp
- GAINSCHA SDK AAR:
Android_GPL_AARv2.2.14.aar(place inapp/libs/)
./gradlew assembleDebug
# Output: app/build/outputs/apk/debug/app-debug.apkSigning credentials are read from keystore.properties (not committed — see below).
./gradlew assembleRelease
# Output: app/build/outputs/apk/release/app-release.apkCreate keystore.properties in the project root (gitignored):
storeFile=sirdab-printer.jks
storePassword=<password>
keyAlias=sirdab-printer
keyPassword=<password>Place sirdab-printer.jks in the app/ directory. Back up both files securely — losing the keystore means you cannot issue updates to existing installations.
Each tablet connects to a different printer, so the printer IP is configured directly on the device. This only needs to be done once per tablet.
- Enable USB debugging: Settings → About tablet → tap Build number 7 times → Settings → Developer options → enable USB debugging
- Connect via USB, then run:
adb install app-release.apk - Open the app once (required by Android to exit the "stopped state" so boot auto-start works)
- Tap Allow on the battery optimisation prompt
- Enter the printer IP (the IP of the GAINSCHA printer on the local WiFi) → tap Save
- Turn off USB debugging: Settings → Developer options → disable USB debugging (or toggle Developer options off entirely)
- From this point the app auto-starts on reboot and requires no further interaction
The printer IP is persisted in SharedPreferences and survives reboots and APK updates.
Advanced settings (label dimensions, print speed, density) can be pushed without touching the tablet UI — useful for bulk adjustments across tablets:
curl -X POST http://<tablet-ip>:8080/config \
-H "Content-Type: application/json" \
-d '{
"printer_port": 8899,
"label_width_mm": 102,
"label_height_mm": 152,
"gap_mm": 3,
"print_speed": 4,
"print_density": 8
}'All endpoints are on localhost:8080. All responses are JSON with CORS headers so fetch() from Fully Kiosk Browser works without restriction.
Submit a print job.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
pdfUrl |
string | ✓ | URL of the PDF to print |
printerIp |
string | Overrides stored config for this job | |
printerPort |
int | Overrides stored config for this job | |
copies |
int | Copies per page (default 1, max 10) |
Success 200:
{ "ok": true, "pages": 1 }Error responses:
| Status | error code |
Cause |
|---|---|---|
| 400 | missing_pdfUrl |
pdfUrl not provided |
| 422 | pdf_error |
PDF could not be downloaded or rendered |
| 503 | printer_unreachable |
Cannot connect to printer after 3 attempts |
| 503 | printer_error |
Printer reported hardware error (paper jam, out of paper, etc.) |
| 504 | job_timeout |
Job exceeded 90s timeout |
Check printer state without printing.
Response 200:
{
"ok": true,
"configured": true,
"printer": {
"state": "ready",
"description": "Ready",
"raw": "00"
}
}Possible state values: ready, printing, paused, out_of_paper, out_of_ribbon, paper_jam, head_open, unreachable, not_configured, error, unknown.
Read current configuration.
Update configuration (partial updates supported — only supplied fields are changed). See fields above in the curl example.
Liveness probe. Returns immediately without touching the printer. Used by the internal watchdog.
{ "ok": true, "service": "printer-companion", "version": "1.0.0" }| Setting | Default | Notes |
|---|---|---|
printer_port |
8899 | GAINSCHA default TCP port |
label_width_mm |
102 | 4" label (101.6 mm) |
label_height_mm |
152 | 6" label (152.4 mm) |
gap_mm |
3 | Gap between labels — adjust if labels misfeed |
print_speed |
4 | 1–15 ips. Lower = darker, more reliable |
print_density |
8 | 0–15. Increase to 10–12 if barcodes scan poorly |
MainActivity (extends GTSPLWIFIActivity)
│ Hosts the HTTP server and printer SDK instance
│ WeakReference exposed to KeepAliveService for watchdog callbacks
│
├── PrintHttpServer (NanoHTTPD, port 8080)
│ Parses incoming requests and delegates to PrinterClient
│
├── PrinterClient
│ Single-thread executor serialises print jobs
│ Downloads PDF → renders to bitmap → sends via GAINSCHA SDK
│ 3× WiFi connection retry with 1.5s back-off
│
├── PdfPageRenderer
│ Uses Android PdfRenderer to rasterise PDF pages at 203 DPI
│ Falls back to WebPageRenderer (WebView) if URL returns HTML
│
├── ConfigManager
│ SharedPreferences-backed config (printer IP, label size, speed, density)
│
├── KeepAliveService (foreground service, START_STICKY)
│ Holds process priority high so Android doesn't kill the activity
│ HTTP watchdog: pings /health every 30s, restarts server on failure
│ onTaskRemoved: restarts MainActivity if task is swiped away
│
└── BootReceiver
Starts KeepAliveService and MainActivity on device boot
| Mechanism | What it protects against |
|---|---|
Foreground service (START_STICKY) |
Android killing the process under memory pressure |
BootReceiver |
Tablet reboot |
android:excludeFromRecents |
Accidental swipe-to-close from recents |
onTaskRemoved in service |
Task removal on firmware that ignores excludeFromRecents |
onBackPressed → moveTaskToBack |
Back button destroying the activity |
| HTTP watchdog (30s interval) | HTTP server silently dying |
| WiFi connect retry (3×, 1.5s) | Transient WiFi disconnects during printing |
| Battery optimisation exemption | Doze mode / OEM power manager suspending the watchdog |