Agente Android — Operación
Ciclo de vida
Boot → MyReceiver (BOOT_COMPLETED) → AutoStartUp service → MainActivity
│
▼
Heartbeat al servidor
│
┌───────────┴────────────┐
▼ ▼
Servidor responde "0" Sin internet / no
(autorizado) autorizado → reintento
│
▼
PlayerActivity (fullscreen)
│
▼
Lee playlist.txt local
│
▼
Reproduce en bucle MP4 / JPG / PNG
Sincronización de contenidos
El servicio en segundo plano AutoStartUp:
- Hace polling al servidor cada N segundos (configurable).
- Compara la lista de archivos del servidor con los locales.
- Descarga los que falten o hayan cambiado, verificando integridad con CRC32. Cuando un archivo se baja completo se crea junto a él un fichero
.ok. - Reescribe
playlist.txt(JSON) con el orden actual y los tiempos por elemento. - El player relee
playlist.txtal iniciar cada vuelta de bucle — los cambios se aplican sin reiniciar la app.
Formatos soportados
| Tipo | Detalles |
|---|---|
| Vídeo | MP4 (H.264). Se reproduce completo en cada vuelta. |
| Imagen estática | JPG, PNG. Tiempo configurado en la playlist. |
| GIF | Detectado pero omitido (no se reproduce). |
| Fallback | Si no hay contenido disponible, muestra una imagen por defecto (sat.jpg). |
Modos de visualización
Dos layouts seleccionables desde los ajustes Admin:
- Stretch a pantalla completa (
relative_layout_video) — usa toda la pantalla, puede deformar el ratio. - Letterbox (
player_content) — mantiene el ratio original, añade bandas si es necesario.
Reporte de estado
En cada heartbeat el agente envía:
accio=esticViu— "estoy vivo" — comprueba autenticación.accio=gps(cada minuto, no sólo coordenadas) — incluye modelo (Build.DEVICE), versión Android (Build.VERSION.SDK_INT), uptime (SystemClock.elapsedRealtime(), incluye tiempo en deep-sleep), cobertura y próximo reinicio.accio=estat&estat=N— estado de descarga / reproducción:- 0 — sin iniciar
- 1 — descargando
- 2 — descargado
- 3 — reproduciendo
Estos valores son los que ves en la columna Estado playlist del panel.
Capturas de pantalla remotas
Desde el panel, Acciones → Captura de pantalla dispara una captura del dispositivo. El servidor inyecta un flag (screenshot:1) en la siguiente respuesta gps (≤ 60 s); AutoStartUp lo detecta y delega en ScreenshotCapture.
El agente intenta tres vías en orden, de más a menos capaz:
Vía 1 — AccessibilityService (recomendado, requiere setup único)
ScreenshotAccessibilityService es un servicio del sistema que captura toda la pantalla del dispositivo — incluye system UI, otras apps, diálogos de error / ANR, lockscreen y, crucialmente, el diálogo "app crashed" si el kiosk ha caído. Sobrevive a caídas de la Activity / Service porque Android reinicia automáticamente los bindings de accesibilidad.
Requisitos:
- Android ≥ 11 (API 30). En versiones anteriores se salta esta vía.
- Activación una sola vez por dispositivo:
- Settings → Accessibility → Apps instaladas → Digital Signage RDS
- Activar el toggle. Android avisa que el servicio podrá leer y manipular eventos — confirma.
- Después de activarlo, no hace falta tocar nada más; el servicio se rebine al arranque automáticamente.
Si el servicio no está habilitado, el agente registra en agent.log al arrancar:
[SCREENSHOT] Full-screen captures disabled — enable 'Digital Signage RDS' in
Settings → Accessibility for captures that work even when the
kiosk app is not foreground.
Vía 2 — Frame del media en reproducción (PlayerActivity)
Si el AccessibilityService no está activo y el modo activo es playlist, el agente:
- Snapshot UI-thread del fichero actual + posición de reproducción
- En un
HandlerThreaddecodifica:- imagen (
.jpg/.png) →BitmapFactory.decodeFile - vídeo →
MediaMetadataRetriever.getFrameAtTime(posMs, OPTION_CLOSEST)
- imagen (
Esto se evita el problema del rectángulo negro causado por PixelCopy(Window) que no puede leer el overlay hardware del SurfaceView donde se renderiza el vídeo.
Vía 3 — PixelCopy de la ventana (KioskBrowserActivity / fallback)
Para el modo Web (WebView, sin overlay hardware) o como último recurso: PixelCopy.request(Window, …) en API ≥ 26 — captura WebView/Surface acelerados por GPU. View.draw(Canvas) en API 24-25.
Común a las tres vías
- JPEG calidad 70 (~150-400 KB típico).
- Firmado con HMAC-SHA256 v2 y subido a
/suport/upload_screenshot.php. - No requiere
MediaProjection: las vías 2 y 3 capturan contenido propio; la vía 1 es un servicio del sistema legítimamente concedido por el admin.
Detalle del flujo en el panel.
Crash recovery
MyExceptionHandler captura excepciones no manejadas y reinicia la app automáticamente. Esto asegura que el dispositivo vuelva a reproducir contenido incluso tras un fallo puntual.
Logs
El agente envía logs operativos al servidor. Para verlos desde el panel: Dispositivos → Acciones → Logs.
Actualizaciones
El APK actualmente comprueba la versión más reciente y, si hay una nueva, descarga el nuevo APK desde el servidor (descarregar_apk.php). El comportamiento exacto depende de la configuración del fabricante; en algunos despliegues la actualización se hace remotamente desde Promotienda.
Limitaciones conocidas
- En Android 8+ los servicios en segundo plano tienen restricciones — el agente puede necesitar permisos adicionales para mantener la sincronización mientras la pantalla está apagada.
- En Android 10+ el almacenamiento scoped obliga a usar
MANAGE_EXTERNAL_STORAGEo MediaStore. Si tu dispositivo es nuevo y la app no puede escribir, ponte en contacto con soporte.
Endpoints HTTP usados
| Endpoint | Función |
|---|---|
suport/WebServiceTablet.php?accio=esticViu |
Heartbeat / auth |
suport/WebServiceTablet.php?accio=estat&estat=N |
Reporte de estado de la playlist |
descarregar_apk.php |
Auto-update del APK |