Agente Android — Operação
Ciclo de vida
Boot → MyReceiver (BOOT_COMPLETED) → AutoStartUp service → MainActivity
│
▼
Heartbeat ao servidor
│
┌───────────┴────────────┐
▼ ▼
Servidor responde "0" Sem internet / não
(autorizado) autorizado → nova tentativa
│
▼
PlayerActivity (ecrã inteiro)
│
▼
Lê playlist.txt local
│
▼
Reproduz em ciclo MP4 / JPG / PNG
Sincronização de conteúdos
O serviço em segundo plano AutoStartUp:
- Faz polling ao servidor a cada N segundos (configurável).
- Compara a lista de ficheiros do servidor com os locais.
- Descarrega os que faltam ou que foram alterados, verificando a integridade com CRC32. Quando um ficheiro é descarregado por completo, é criado junto dele um ficheiro
.ok. - Reescreve
playlist.txt(JSON) com a ordem atual e os tempos por elemento. - O leitor relê
playlist.txtao iniciar cada volta do ciclo — as alterações são aplicadas sem reiniciar a app.
Formatos suportados
| Tipo | Detalhes |
|---|---|
| Vídeo | MP4 (H.264). Reproduzido por completo em cada volta. |
| Imagem estática | JPG, PNG. Tempo configurado na playlist. |
| GIF | Detetado mas ignorado (não é reproduzido). |
| Fallback | Se não houver conteúdo disponível, mostra uma imagem por defeito (sat.jpg). |
Modos de visualização
Dois layouts selecionáveis a partir das definições Admin:
- Stretch a ecrã inteiro (
relative_layout_video) — usa todo o ecrã, pode deformar o ratio. - Letterbox (
player_content) — mantém o ratio original, adiciona bandas se necessário.
Reporte de estado
Em cada heartbeat o agente envia:
accio=esticViu— "estou vivo" — verifica autenticação.accio=gps(a cada minuto, não apenas coordenadas) — inclui modelo (Build.DEVICE), versão do Android (Build.VERSION.SDK_INT), uptime (SystemClock.elapsedRealtime(), inclui tempo em deep-sleep), cobertura e próximo reinício.accio=estat&estat=N— estado de descarga / reprodução:- 0 — não iniciado
- 1 — a descarregar
- 2 — descarregado
- 3 — a reproduzir
Estes valores são os que vê na coluna Estado playlist do painel.
Capturas de ecrã remotas
A partir do painel, Ações → Captura de ecrã despoleta uma captura do dispositivo. O servidor injeta uma flag (screenshot:1) na próxima resposta gps (≤ 60 s); AutoStartUp deteta-a e delega em ScreenshotCapture.
O agente tenta três vias por ordem, da mais capaz para a menos:
Via 1 — AccessibilityService (recomendada, requer setup único)
ScreenshotAccessibilityService é um serviço do sistema que captura todo o ecrã do dispositivo — inclui system UI, outras apps, diálogos de erro / ANR, lockscreen e, crucialmente, o diálogo "app crashed" se o kiosk tiver caído. Sobrevive a falhas da Activity / Service porque o Android reinicia automaticamente os bindings de acessibilidade.
Requisitos:
- Android ≥ 11 (API 30). Em versões anteriores esta via é ignorada.
- Ativação uma única vez por dispositivo:
- Settings → Accessibility → Apps instaladas → Digital Signage RDS
- Ativar o toggle. O Android avisa que o serviço poderá ler e manipular eventos — confirme.
- Após ativar, não é preciso mexer em mais nada; o serviço é reassociado no arranque automaticamente.
Se o serviço não estiver ativo, o agente regista em agent.log no arranque:
[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 da media em reprodução (PlayerActivity)
Se o AccessibilityService não estiver ativo e o modo ativo for playlist, o agente:
- Snapshot UI-thread do ficheiro atual + posição de reprodução
- Num
HandlerThreaddescodifica:- imagem (
.jpg/.png) →BitmapFactory.decodeFile - vídeo →
MediaMetadataRetriever.getFrameAtTime(posMs, OPTION_CLOSEST)
- imagem (
Isto evita o problema do retângulo preto causado por PixelCopy(Window) que não consegue ler o overlay hardware do SurfaceView onde o vídeo é renderizado.
Via 3 — PixelCopy da janela (KioskBrowserActivity / fallback)
Para o modo Web (WebView, sem overlay hardware) ou como último recurso: PixelCopy.request(Window, …) em API ≥ 26 — captura WebView/Surface acelerados por GPU. View.draw(Canvas) em API 24-25.
Comum às três vias
- JPEG qualidade 70 (~150–400 KB típico).
- Assinado com HMAC-SHA256 v2 e enviado a
/suport/upload_screenshot.php. - Não requer
MediaProjection: as vias 2 e 3 capturam conteúdo próprio; a via 1 é um serviço do sistema legitimamente concedido pelo admin.
Crash recovery
MyExceptionHandler captura exceções não tratadas e reinicia a app automaticamente. Isto garante que o dispositivo volta a reproduzir conteúdo mesmo após uma falha pontual.
Logs
O agente envia logs operacionais ao servidor. Para os consultar a partir do painel: Dispositivos → Ações → Logs.
Atualizações
O APK atualmente verifica a versão mais recente e, se houver uma nova, descarrega o novo APK a partir do servidor (descarregar_apk.php). O comportamento exato depende da configuração do fabricante; em alguns deploys a atualização é feita remotamente a partir da Promotienda.
Limitações conhecidas
- No Android 8+ os serviços em segundo plano têm restrições — o agente pode precisar de permissões adicionais para manter a sincronização enquanto o ecrã está desligado.
- No Android 10+ o armazenamento scoped obriga a usar
MANAGE_EXTERNAL_STORAGEou MediaStore. Se o seu dispositivo é novo e a app não consegue escrever, contacte o suporte.
Endpoints HTTP usados
| Endpoint | Função |
|---|---|
suport/WebServiceTablet.php?accio=esticViu |
Heartbeat / auth |
suport/WebServiceTablet.php?accio=estat&estat=N |
Reporte de estado da playlist |
descarregar_apk.php |
Auto-update do APK |