Agent Android — Operació
Cicle de vida
Boot → MyReceiver (BOOT_COMPLETED) → AutoStartUp service → MainActivity
│
▼
Heartbeat al servidor
│
┌───────────┴────────────┐
▼ ▼
Servidor respon "0" Sense internet / no
(autoritzat) autoritzat → reintent
│
▼
PlayerActivity (fullscreen)
│
▼
Llegeix playlist.txt local
│
▼
Reprodueix en bucle MP4 / JPG / PNG
Sincronització de continguts
El servei en segon pla AutoStartUp:
- Fa polling al servidor cada N segons (configurable).
- Compara la llista d'arxius del servidor amb els locals.
- Descarrega els que faltin o hagin canviat, verificant integritat amb CRC32. Quan un arxiu es baixa complet es crea al costat un fitxer
.ok. - Reescriu
playlist.txt(JSON) amb l'ordre actual i els temps per element. - El player rellegeix
playlist.txta l'inici de cada volta de bucle — els canvis s'apliquen sense reiniciar l'app.
Formats suportats
| Tipus | Detalls |
|---|---|
| Vídeo | MP4 (H.264). Es reprodueix complet a cada volta. |
| Imatge estàtica | JPG, PNG. Temps configurat a la playlist. |
| GIF | Detectat però omès (no es reprodueix). |
| Fallback | Si no hi ha contingut disponible, mostra una imatge per defecte (sat.jpg). |
Modes de visualització
Dos layouts seleccionables des dels ajustaments Admin:
- Stretch a pantalla completa (
relative_layout_video) — utilitza tota la pantalla, pot deformar el ràtio. - Letterbox (
player_content) — manté el ràtio original, afegeix bandes si cal.
Reporte d'estat
A cada heartbeat l'agent envia:
accio=esticViu— "estic viu" — comprova autenticació.accio=gps(cada minut, no només coordenades) — inclou model (Build.DEVICE), versió Android (Build.VERSION.SDK_INT), uptime (SystemClock.elapsedRealtime(), inclou temps en deep-sleep), cobertura i pròxim reinici.accio=estat&estat=N— estat de descàrrega / reproducció:- 0 — sense iniciar
- 1 — descarregant
- 2 — descarregat
- 3 — reproduint
Aquests valors són els que veus a la columna Estat playlist del panell.
Captures de pantalla remotes
Des del panell, Accions → Captura de pantalla dispara una captura del dispositiu. El servidor injecta un flag (screenshot:1) a la següent resposta gps (≤ 60 s); AutoStartUp ho detecta i delega a ScreenshotCapture.
L'agent intenta tres vies en ordre, de més a menys capaç:
Via 1 — AccessibilityService (recomanat, requereix setup únic)
ScreenshotAccessibilityService és un servei del sistema que captura tota la pantalla del dispositiu — inclou system UI, altres apps, diàlegs d'error / ANR, lockscreen i, crucialment, el diàleg "app crashed" si el kiosk ha caigut. Sobreviu a caigudes de l'Activity / Service perquè Android reinicia automàticament els bindings d'accessibilitat.
Requisits:
- Android ≥ 11 (API 30). En versions anteriors es salta aquesta via.
- Activació una sola vegada per dispositiu:
- Settings → Accessibility → Apps instal·lades → Digital Signage RDS
- Activar el toggle. Android avisa que el servei podrà llegir i manipular esdeveniments — confirma.
- Després d'activar-lo, no cal tocar res més; el servei es rebina a l'arrencada automàticament.
Si el servei no està habilitat, l'agent registra a agent.log en arrencar:
[SCREENSHOT] Full-screen captures disabled — enable 'Digital Signage RDS' in
Settings → Accessibility for captures that work even when the
kiosk app is not foreground.
Via 2 — Frame del media en reproducció (PlayerActivity)
Si l'AccessibilityService no està actiu i el mode actiu és playlist, l'agent:
- Snapshot UI-thread del fitxer actual + posició de reproducció
- En un
HandlerThreaddescodifica:- imatge (
.jpg/.png) →BitmapFactory.decodeFile - vídeo →
MediaMetadataRetriever.getFrameAtTime(posMs, OPTION_CLOSEST)
- imatge (
Això evita el problema del rectangle negre causat per PixelCopy(Window) que no pot llegir l'overlay hardware del SurfaceView on es renderitza el vídeo.
Via 3 — PixelCopy de la finestra (KioskBrowserActivity / fallback)
Per al mode Web (WebView, sense overlay hardware) o com a últim recurs: PixelCopy.request(Window, …) en API ≥ 26 — captura WebView/Surface accelerats per GPU. View.draw(Canvas) en API 24-25.
Comú a les tres vies
- JPEG qualitat 70 (~150-400 KB típic).
- Signat amb HMAC-SHA256 v2 i pujat a
/suport/upload_screenshot.php. - No requereix
MediaProjection: les vies 2 i 3 capturen contingut propi; la via 1 és un servei del sistema legítimament concedit per l'admin.
Crash recovery
MyExceptionHandler captura excepcions no gestionades i reinicia l'app automàticament. Això assegura que el dispositiu torni a reproduir contingut fins i tot després d'una fallada puntual.
Logs
L'agent envia logs operatius al servidor. Per veure'ls des del panell: Dispositius → Accions → Logs.
Actualitzacions
L'APK actualment comprova la versió més recent i, si n'hi ha una de nova, descarrega el nou APK des del servidor (descarregar_apk.php). El comportament exacte depèn de la configuració del fabricant; en alguns desplegaments l'actualització es fa remotament des de Promotienda.
Limitacions conegudes
- A Android 8+ els serveis en segon pla tenen restriccions — l'agent pot necessitar permisos addicionals per mantenir la sincronització mentre la pantalla està apagada.
- A Android 10+ l'emmagatzematge scoped obliga a usar
MANAGE_EXTERNAL_STORAGEo MediaStore. Si el teu dispositiu és nou i l'app no pot escriure, posa't en contacte amb suport.
Endpoints HTTP usats
| Endpoint | Funció |
|---|---|
suport/WebServiceTablet.php?accio=esticViu |
Heartbeat / auth |
suport/WebServiceTablet.php?accio=estat&estat=N |
Reporte d'estat de la playlist |
descarregar_apk.php |
Auto-update de l'APK |