package com.jttools.datacontrol.admin import android.app.admin.DevicePolicyManager import android.content.Context import android.os.Build import android.os.UserManager import com.jttools.datacontrol.log.AppLog /** * Mode « propriétaire de l'appareil » (Device Owner), provisionné une fois par adb : * adb shell dpm set-device-owner com.jttools.datacontrol/.admin.AppDeviceAdminReceiver * * DataControl impose alors au système : VPN toujours actif (connexions bloquées hors tunnel), réglages * VPN / date et heure / DNS privé verrouillés, pas de réinitialisation, pas de mode sans échec, pas * d'autre utilisateur, désinstallation impossible. Sans ce mode, toutes ces fonctions sont inertes. */ object OwnerPolicy { /** Restrictions posées tant que la protection est active (le débogage USB reste permis : maintenance). */ private val RESTRICTIONS = buildList { add(UserManager.DISALLOW_CONFIG_VPN) add(UserManager.DISALLOW_CONFIG_DATE_TIME) add(UserManager.DISALLOW_FACTORY_RESET) add(UserManager.DISALLOW_SAFE_BOOT) add(UserManager.DISALLOW_ADD_USER) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) add(UserManager.DISALLOW_CONFIG_PRIVATE_DNS) } /** État voulu, appliqué tel quel ; [lockdown] = connexions bloquées hors VPN. */ data class Target(val protect: Boolean, val lockdown: Boolean) private var applied: Target? = null fun isDeviceOwner(context: Context): Boolean = AdminController.isDeviceOwner(context) private fun dpm(context: Context) = context.getSystemService(DevicePolicyManager::class.java) /** Applique [target] si DataControl est propriétaire et que l'état a changé. Renvoie faux en cas d'échec. */ fun sync(context: Context, target: Target, force: Boolean = false): Boolean { if (!isDeviceOwner(context)) { applied = null return true } if (!force && target == applied) return true val dpm = dpm(context) val admin = AdminController.component(context) return try { if (target.protect) { // Le système accorde lui-même l'autorisation VPN au paquet désigné : aucune fenêtre de consentement. dpm.setAlwaysOnVpnPackage(admin, context.packageName, target.lockdown) RESTRICTIONS.forEach { dpm.addUserRestriction(admin, it) } dpm.setUninstallBlocked(admin, context.packageName, true) requireAutoTime(context) } else { dpm.setAlwaysOnVpnPackage(admin, null, false) RESTRICTIONS.forEach { dpm.clearUserRestriction(admin, it) } dpm.setUninstallBlocked(admin, context.packageName, false) } applied = target AppLog.i("Propriétaire", when { !target.protect -> "Protections levées (contrôle désactivé ou appareil non associé)" target.lockdown -> "VPN toujours actif, connexions bloquées hors VPN, réglages verrouillés" else -> "VPN toujours actif SANS blocage hors VPN (serveur injoignable), réglages verrouillés" }) true } catch (e: Exception) { AppLog.e("Propriétaire", "Application des protections impossible", e) false } } @Suppress("DEPRECATION") private fun requireAutoTime(context: Context) { val dpm = dpm(context) val admin = AdminController.component(context) runCatching { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { dpm.setAutoTimeEnabled(admin, true) dpm.setAutoTimeZoneEnabled(admin, true) } else { dpm.setAutoTimeRequired(admin, true) } } } /** VPN toujours actif imposé par DataControl, et avec quel blocage. */ fun alwaysOnState(context: Context): Pair { if (!isDeviceOwner(context)) return false to false val dpm = dpm(context) val admin = AdminController.component(context) return runCatching { val pkg = dpm.getAlwaysOnVpnPackage(admin) val lockdown = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && dpm.isAlwaysOnVpnLockdownEnabled(admin) (pkg == context.packageName) to lockdown }.getOrDefault(false to false) } /** * Sortie du mode propriétaire (après vérification du mot de passe) : protections levées, puis * DataControl redevient une application ordinaire (l'administrateur d'appareil simple reste actif). */ fun release(context: Context): Boolean { if (!isDeviceOwner(context)) return true sync(context, Target(protect = false, lockdown = false), force = true) return runCatching { dpm(context).clearDeviceOwnerApp(context.packageName) applied = null AppLog.w("Propriétaire", "Mode propriétaire de l'appareil retiré") true }.getOrElse { AppLog.e("Propriétaire", "Retrait du mode propriétaire impossible", it) false } } }