package com.jttools.datacontrol.guard import android.app.admin.DevicePolicyManager import android.content.Context import android.content.Intent import android.os.Build import android.os.Bundle import android.view.WindowManager import androidx.activity.ComponentActivity import androidx.activity.OnBackPressedCallback import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Lock import androidx.compose.material3.Button import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.lifecycleScope import com.jttools.datacontrol.R import com.jttools.datacontrol.admin.AdminController import com.jttools.datacontrol.data.SettingsRepository import com.jttools.datacontrol.remote.RemoteSync import com.jttools.datacontrol.ui.theme.DataControlTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject /** * Écran de verrouillage parental plein écran : affiché sur commande du parent (« plock »), il reste * au premier plan jusqu'à la saisie d'un code à usage unique généré par le serveur (action "unlock"). * * Le code remplace le mot de passe local : seul le parent, en dictant le code, peut déverrouiller. * En mode propriétaire de l'appareil, l'écran est épinglé (lock task) : accueil et récents neutralisés. * Sinon, l'activité se remet d'elle-même au premier plan si l'enfant tente de la quitter. */ @AndroidEntryPoint class ParentalLockActivity : ComponentActivity() { @Inject lateinit var remoteSync: RemoteSync @Inject lateinit var settings: SettingsRepository private var unlocked = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { setShowWhenLocked(true) setTurnScreenOn(true) } window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) // Le bouton Retour ne ferme pas le verrou. onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { override fun handleOnBackPressed() = Unit }) setContent { DataControlTheme { var code by remember { mutableStateOf("") } var error by remember { mutableStateOf(null) } var busy by remember { mutableStateOf(false) } val scope = androidx.compose.runtime.rememberCoroutineScope() Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.errorContainer) { Column( Modifier.fillMaxSize().padding(32.dp), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { Icon(Icons.Filled.Lock, contentDescription = null, modifier = Modifier.size(72.dp)) Spacer(Modifier.height(20.dp)) Text( getString(R.string.parental_lock_title), style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center ) Spacer(Modifier.height(10.dp)) Text( getString(R.string.parental_lock_message), style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center ) Spacer(Modifier.height(24.dp)) OutlinedTextField( value = code, onValueChange = { code = it.take(20); error = null }, label = { Text(getString(R.string.parental_lock_code_label)) }, keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Characters), isError = error != null, singleLine = true, modifier = Modifier.fillMaxWidth() ) error?.let { Spacer(Modifier.height(6.dp)) Text(it, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) } Spacer(Modifier.height(20.dp)) Button( enabled = !busy, onClick = { busy = true scope.launch { val result = remoteSync.verifyUnlock(code, "unlock") if (result == null) { settings.setParentalLocked(false) // l'observateur ferme l'écran } else { error = result busy = false } } }, modifier = Modifier.fillMaxWidth() ) { Text(getString(R.string.parental_lock_unlock)) } } } } } // Le parent peut aussi déverrouiller à distance (« punlock ») : on ferme dès que l'état repasse à faux. lifecycleScope.launch { settings.settingsFlow.map { it.parentalLocked }.collect { locked -> if (!locked) { unlocked = true stopPinning() finish() } } } } override fun onResume() { super.onResume() startPinning() } override fun onStop() { super.onStop() // Tant que le verrou tient, toute tentative de quitter (Accueil, bascule) ramène l'écran. if (!unlocked && !isFinishing) { startActivity(intent(this)) } } /** Épingle l'écran en mode propriétaire : l'accueil et les récents ne peuvent plus l'écarter. */ private fun startPinning() { if (!AdminController.isDeviceOwner(this)) return runCatching { val dpm = getSystemService(DevicePolicyManager::class.java) dpm.setLockTaskPackages(AdminController.component(this), arrayOf(packageName)) startLockTask() } } private fun stopPinning() { runCatching { stopLockTask() } } companion object { fun intent(context: Context): Intent = Intent(context, ParentalLockActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) } }