Files
DataControl/app/src/main/java/com/jttools/datacontrol/ui/SystemActions.kt
T
tuxgyverandClaude Opus 5.5 33ea4ff947 feat: tunnel WireGuard vers le serveur familyd et mode propriétaire de l'appareil
- Firebase et le pare-feu local (trou noir, liste blanche par application, coupure cyclique locale) sont retirés
- Client WireGuard officiel (GoBackend) : tout le trafic passe par le serveur familyd, qui filtre
- Appairage par code ou lien familyd:// ; clé privée WireGuard et jeton chiffrés par l'Android Keystore
- Remontées par le tunnel : état, temps d'écran, applications installées, évènements (file persistante)
- Mode propriétaire (Device Owner) : VPN toujours actif avec blocage hors VPN, réglages verrouillés,
  garde-fou si le serveur ne répond plus pendant 15 min
- Plan de la refonte : docs/PLAN-wireguard.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9CBCVP5oDu2sfoYPisNCQ
2026-10-25 14:44:18 +01:00

133 lines
5.5 KiB
Kotlin

package com.jttools.datacontrol.ui
import android.annotation.SuppressLint
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import com.jttools.datacontrol.vpn.TunnelManager
/**
* Regroupe les autorisations système nécessaires (VPN, notifications, batterie) pour que
* les écrans Compose puissent les demander sans dépendre directement de l'Activity.
*/
class SystemActions(private val activity: ComponentActivity) {
private var onVpnResult: ((Boolean) -> Unit)? = null
private var onNotificationResult: ((Boolean) -> Unit)? = null
private val vpnLauncher: ActivityResultLauncher<Intent> = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
onVpnResult?.invoke(result.resultCode == android.app.Activity.RESULT_OK)
}
private val notificationLauncher: ActivityResultLauncher<String> = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted -> onNotificationResult?.invoke(granted) }
private var onLocationResult: ((Boolean) -> Unit)? = null
private var onBackgroundLocationResult: ((Boolean) -> Unit)? = null
private var onAdminResult: ((Boolean) -> Unit)? = null
private val locationLauncher: ActivityResultLauncher<Array<String>> = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { granted -> onLocationResult?.invoke(granted.values.any { it }) }
private val backgroundLocationLauncher: ActivityResultLauncher<String> = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted -> onBackgroundLocationResult?.invoke(granted) }
private val adminLauncher: ActivityResultLauncher<Intent> = activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result -> onAdminResult?.invoke(result.resultCode == android.app.Activity.RESULT_OK) }
fun hasLocationPermission(): Boolean = ContextCompat.checkSelfPermission(
activity, android.Manifest.permission.ACCESS_FINE_LOCATION
) == android.content.pm.PackageManager.PERMISSION_GRANTED
fun hasBackgroundLocationPermission(): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || ContextCompat.checkSelfPermission(
activity, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == android.content.pm.PackageManager.PERMISSION_GRANTED
fun requestLocationPermission(onResult: (Boolean) -> Unit) {
onLocationResult = onResult
locationLauncher.launch(
arrayOf(android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_COARSE_LOCATION)
)
}
/** À demander APRÈS la position au premier plan (exigence Android 11+). */
fun requestBackgroundLocationPermission(onResult: (Boolean) -> Unit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { onResult(true); return }
onBackgroundLocationResult = onResult
backgroundLocationLauncher.launch(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
fun isGuardEnabled(): Boolean = com.jttools.datacontrol.guard.AppGuardService.isEnabled(activity)
fun openAccessibilitySettings() {
activity.startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
fun isAdminActive(): Boolean = com.jttools.datacontrol.admin.AdminController.isActive(activity)
fun requestAdmin(onResult: (Boolean) -> Unit) {
onAdminResult = onResult
adminLauncher.launch(com.jttools.datacontrol.admin.AdminController.requestIntent(activity))
}
fun deactivateAdmin() = com.jttools.datacontrol.admin.AdminController.deactivate(activity)
fun isVpnPrepared(): Boolean = TunnelManager.isPrepared(activity)
fun requestVpnPermission(onResult: (Boolean) -> Unit) {
val intent = VpnService.prepare(activity)
if (intent == null) {
onResult(true)
} else {
onVpnResult = onResult
vpnLauncher.launch(intent)
}
}
fun hasNotificationPermission(): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return true
return ContextCompat.checkSelfPermission(
activity, android.Manifest.permission.POST_NOTIFICATIONS
) == android.content.pm.PackageManager.PERMISSION_GRANTED
}
fun requestNotificationPermission(onResult: (Boolean) -> Unit) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
onResult(true)
return
}
onNotificationResult = onResult
notificationLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS)
}
fun isIgnoringBatteryOptimizations(): Boolean {
val pm = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
return pm.isIgnoringBatteryOptimizations(activity.packageName)
}
@SuppressLint("BatteryLife")
fun requestIgnoreBatteryOptimizations() {
val intent = Intent(
Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
Uri.parse("package:${activity.packageName}")
)
activity.startActivity(intent)
}
}