feat: faire sonner le téléphone à distance et accusé de réception des commandes
Nouvelle action « Faire sonner » (commandes ring / ring_stop) : sonnerie d'alarme à plein volume même en silencieux, vibration et notification « touchez pour arrêter », arrêt automatique au bout de la durée demandée. Chaque commande reçue est désormais acquittée au serveur (exécutée ou en échec, avec le détail), ce qui complète l'audit côté espace parents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9CBCVP5oDu2sfoYPisNCQ
This commit is contained in:
1 parent
7af250cc1c
commit
ed8be296ba
5 files changed
+217
-23
No files matched your search
@@ -33,7 +33,8 @@ import javax.inject.Singleton
|
||||
class RemoteControl @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val remote: RemoteSync,
|
||||
private val settings: SettingsRepository
|
||||
private val settings: SettingsRepository,
|
||||
private val ringer: Ringer
|
||||
) {
|
||||
/** Instant (temps monotone) jusqu'auquel l'écran est partagé ; 0 = pas de partage. */
|
||||
private val _screenShareUntil = MutableStateFlow(0L)
|
||||
@@ -58,8 +59,11 @@ class RemoteControl @Inject constructor(
|
||||
continue
|
||||
}
|
||||
for (i in 0 until commands.length()) {
|
||||
runCatching { handle(commands.getJSONObject(i)) }
|
||||
.onFailure { AppLog.w("Commande", "Commande ignorée : ${it.message}") }
|
||||
val cmd = commands.getJSONObject(i)
|
||||
val outcome = runCatching { handle(cmd) }
|
||||
val (status, detail) = outcome.getOrNull()
|
||||
?: ("failed" to (outcome.exceptionOrNull()?.message ?: "erreur"))
|
||||
remote.ackCommand(cmd.optString("id"), status, detail)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -76,35 +80,43 @@ class RemoteControl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handle(cmd: JSONObject) {
|
||||
when (cmd.optString("type")) {
|
||||
"lock" -> lock()
|
||||
"mute" -> setMuted(true)
|
||||
"unmute" -> setMuted(false)
|
||||
"screen_start" -> {
|
||||
val seconds = cmd.optInt("seconds", 120).coerceIn(10, 300)
|
||||
_screenShareUntil.value = SystemClock.elapsedRealtime() + seconds * 1000L
|
||||
AppLog.i("Commande", "Partage d'écran démarré ($seconds s)")
|
||||
}
|
||||
"screen_stop" -> {
|
||||
_screenShareUntil.value = 0L
|
||||
AppLog.i("Commande", "Partage d'écran arrêté")
|
||||
}
|
||||
/** Renvoie (statut, détail) pour l'audit : "done" ou "failed". */
|
||||
private fun handle(cmd: JSONObject): Pair<String, String> = when (cmd.optString("type")) {
|
||||
"lock" -> lock()
|
||||
"mute" -> setMuted(true)
|
||||
"unmute" -> setMuted(false)
|
||||
"screen_start" -> {
|
||||
val seconds = cmd.optInt("seconds", 120).coerceIn(10, 300)
|
||||
_screenShareUntil.value = SystemClock.elapsedRealtime() + seconds * 1000L
|
||||
AppLog.i("Commande", "Partage d'écran démarré ($seconds s)")
|
||||
"done" to "partage démarré"
|
||||
}
|
||||
"screen_stop" -> {
|
||||
_screenShareUntil.value = 0L
|
||||
AppLog.i("Commande", "Partage d'écran arrêté")
|
||||
"done" to ""
|
||||
}
|
||||
"ring" -> ringer.start(cmd.optInt("seconds", 60))
|
||||
"ring_stop" -> {
|
||||
ringer.stop(silent = false)
|
||||
"done" to ""
|
||||
}
|
||||
else -> "failed" to "commande inconnue"
|
||||
}
|
||||
|
||||
private fun lock() {
|
||||
private fun lock(): Pair<String, String> {
|
||||
val dpm = context.getSystemService(DevicePolicyManager::class.java)
|
||||
if (!AdminController.isActive(context)) {
|
||||
AppLog.w("Commande", "Verrouillage impossible : DataControl n'est pas administrateur de l'appareil")
|
||||
return
|
||||
return "failed" to "DataControl n'est pas administrateur de l'appareil"
|
||||
}
|
||||
runCatching { dpm.lockNow() }
|
||||
.onSuccess { AppLog.i("Commande", "Téléphone verrouillé / mis en veille") }
|
||||
.onFailure { AppLog.w("Commande", "Verrouillage refusé : ${it.message}") }
|
||||
return runCatching { dpm.lockNow() }.fold(
|
||||
onSuccess = { AppLog.i("Commande", "Téléphone verrouillé / mis en veille"); "done" to "" },
|
||||
onFailure = { AppLog.w("Commande", "Verrouillage refusé : ${it.message}"); "failed" to (it.message ?: "refusé") }
|
||||
)
|
||||
}
|
||||
|
||||
private fun setMuted(muted: Boolean) {
|
||||
private fun setMuted(muted: Boolean): Pair<String, String> {
|
||||
val am = context.getSystemService(AudioManager::class.java)
|
||||
val dir = if (muted) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE
|
||||
val streams = intArrayOf(
|
||||
@@ -117,6 +129,7 @@ class RemoteControl @Inject constructor(
|
||||
if (runCatching { am.adjustStreamVolume(s, dir, 0) }.isSuccess) any = true
|
||||
}
|
||||
AppLog.i("Commande", if (muted) "Son coupé (${if (any) "ok" else "refusé"})" else "Son rétabli")
|
||||
return if (any) "done" to "" else "failed" to "aucun flux audio modifiable (accès « Ne pas déranger » ?)"
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
||||
Reference in new issue
Block a user