Agent Android — Exploitation
Cycle de vie
Boot → MyReceiver (BOOT_COMPLETED) → AutoStartUp service → MainActivity
│
▼
Heartbeat au serveur
│
┌───────────┴────────────┐
▼ ▼
Serveur répond "0" Pas d'internet / non
(autorisé) autorisé → réessai
│
▼
PlayerActivity (plein écran)
│
▼
Lit playlist.txt local
│
▼
Boucle MP4 / JPG / PNG
Synchronisation des contenus
Le service en arrière-plan AutoStartUp :
- Sonde le serveur toutes les N secondes (configurable).
- Compare la liste de fichiers du serveur avec celle en local.
- Télécharge ceux qui manquent ou ont changé, en vérifiant l'intégrité par CRC32. Lorsqu'un fichier est entièrement téléchargé, un fichier compagnon
.okest créé à côté. - Réécrit
playlist.txt(JSON) avec l'ordre actuel et les durées par élément. - Le lecteur relit
playlist.txtau début de chaque tour de boucle — les changements s'appliquent sans redémarrer l'app.
Formats supportés
| Type | Détails |
|---|---|
| Vidéo | MP4 (H.264). Joue intégralement à chaque tour. |
| Image statique | JPG, PNG. Durée configurée dans la playlist. |
| GIF | Détecté mais omis (non joué). |
| Fallback | Si aucun contenu n'est disponible, affiche une image par défaut (sat.jpg). |
Modes d'affichage
Deux layouts sélectionnables depuis les paramètres Admin :
- Stretch plein écran (
relative_layout_video) — utilise tout l'écran, peut déformer le ratio. - Letterbox (
player_content) — conserve le ratio original, ajoute des bandes si nécessaire.
Rapport d'état
À chaque heartbeat l'agent envoie :
accio=esticViu— "je suis en vie" — vérifie l'authentification.accio=gps(chaque minute, pas seulement les coordonnées) — inclut modèle (Build.DEVICE), version Android (Build.VERSION.SDK_INT), uptime (SystemClock.elapsedRealtime(), inclut le temps en deep-sleep), couverture et prochain redémarrage.accio=estat&estat=N— état téléchargement / lecture :- 0 — non démarré
- 1 — téléchargement
- 2 — téléchargé
- 3 — en lecture
Ces valeurs sont celles affichées dans la colonne État playlist du panneau.
Captures d'écran distantes
Depuis le panneau, Actions → Capture d'écran déclenche une capture de l'appareil. Le serveur injecte un drapeau (screenshot:1) dans la prochaine réponse gps (≤ 60 s) ; AutoStartUp le détecte et délègue à ScreenshotCapture.
L'agent essaie trois voies dans l'ordre, de la plus à la moins capable :
Voie 1 — AccessibilityService (recommandé, setup unique requis)
ScreenshotAccessibilityService est un service système qui capture tout l'écran de l'appareil — inclut l'UI système, autres apps, dialogues d'erreur / ANR, lockscreen et, surtout, le dialogue "app crashed" si le kiosk a planté. Survit aux crashs d'Activity / Service car Android relance automatiquement les bindings d'accessibilité.
Exigences :
- Android ≥ 11 (API 30). Sur versions antérieures, cette voie est sautée.
- Activation une seule fois par appareil :
- Settings → Accessibility → Apps installées → Digital Signage RDS
- Activer le toggle. Android avertit que le service pourra lire et manipuler les événements — confirmez.
- Après activation, rien d'autre à faire ; le service se relie au démarrage automatiquement.
Si le service n'est pas activé, l'agent enregistre dans agent.log au démarrage :
[SCREENSHOT] Full-screen captures disabled — enable 'Digital Signage RDS' in
Settings → Accessibility for captures that work even when the
kiosk app is not foreground.
Voie 2 — Frame du média en lecture (PlayerActivity)
Si l'AccessibilityService n'est pas actif et que le mode actif est playlist, l'agent :
- Snapshot UI-thread du fichier actuel + position de lecture
- Sur un
HandlerThreaddécode :- image (
.jpg/.png) →BitmapFactory.decodeFile - vidéo →
MediaMetadataRetriever.getFrameAtTime(posMs, OPTION_CLOSEST)
- image (
Cela évite le problème du rectangle noir causé par PixelCopy(Window) qui ne peut pas lire l'overlay matériel du SurfaceView où la vidéo est rendue.
Voie 3 — PixelCopy de la fenêtre (KioskBrowserActivity / fallback)
Pour le mode Web (WebView, sans overlay matériel) ou en dernier recours : PixelCopy.request(Window, …) sur API ≥ 26 — capture WebView/Surface accélérés par GPU. View.draw(Canvas) sur API 24-25.
Commun aux trois voies
- JPEG qualité 70 (~150-400 KB typique).
- Signé avec HMAC-SHA256 v2 et envoyé à
/suport/upload_screenshot.php. - Ne nécessite pas
MediaProjection: les voies 2 et 3 capturent du contenu propre ; la voie 1 est un service système légitimement accordé par l'admin.
Détail du flux dans le panneau.
Crash recovery
MyExceptionHandler capture les exceptions non gérées et redémarre l'app automatiquement. Cela garantit que l'appareil reprend la lecture du contenu même après une défaillance ponctuelle.
Logs
L'agent envoie des logs opérationnels au serveur. Pour les voir depuis le panneau : Appareils → Actions → Logs.
Mises à jour
L'APK vérifie actuellement la version la plus récente et, si une nouvelle existe, télécharge le nouveau APK depuis le serveur (descarregar_apk.php). Le comportement exact dépend de la configuration du fabricant ; dans certains déploiements la mise à jour est poussée à distance depuis Promotienda.
Limitations connues
- Sur Android 8+, les services d'arrière-plan ont des restrictions — l'agent peut nécessiter des permissions supplémentaires pour maintenir la synchronisation quand l'écran est éteint.
- Sur Android 10+, le stockage scoped oblige à utiliser
MANAGE_EXTERNAL_STORAGEou MediaStore. Si votre appareil est récent et que l'app ne peut pas écrire, contactez le support.
Endpoints HTTP utilisés
| Endpoint | Fonction |
|---|---|
suport/WebServiceTablet.php?accio=esticViu |
Heartbeat / auth |
suport/WebServiceTablet.php?accio=estat&estat=N |
Rapport d'état de la playlist |
descarregar_apk.php |
Auto-update de l'APK |