Refactor security logging

Bump version number
This commit is contained in:
BinTianqi
2025-10-14 23:35:28 +08:00
parent 9e1d18b8e7
commit 9c796690e3
14 changed files with 603 additions and 347 deletions

View File

@@ -14,21 +14,28 @@ import android.content.pm.PackageInstaller
import android.os.Build.VERSION
import android.util.Log
import androidx.annotation.RequiresApi
import com.bintianqi.owndroid.MyApplication
import com.bintianqi.owndroid.MyNotificationChannel
import com.bintianqi.owndroid.NotificationType
import com.bintianqi.owndroid.NotificationUtils
import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SP
import com.bintianqi.owndroid.ShortcutUtils
import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuBinderWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray
import java.io.OutputStream
@SuppressLint("PrivateApi")
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
@@ -166,230 +173,336 @@ fun handleNetworkLogs(context: Context, batchToken: Long) {
buffer.close()
}
@RequiresApi(24)
fun processSecurityLogs(securityEvents: List<SecurityLog.SecurityEvent>, outputStream: OutputStream) {
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
val buffer = outputStream.bufferedWriter()
securityEvents.forEachIndexed { index, event ->
val item = buildJsonObject {
put("time", event.timeNanos / 1000)
put("tag", event.tag)
if(VERSION.SDK_INT >= 28) put("level", event.logLevel)
if(VERSION.SDK_INT >= 28) put("id", event.id)
parseSecurityEventData(event).let { if(it != null) put("data", it) }
}
buffer.write(json.encodeToString(item))
if(index < securityEvents.size - 1) buffer.write(",")
}
buffer.close()
@Serializable
class SecurityEvent(
val id: Long?, val tag: Int, val level: Int?, val time: Long, val data: JsonObject?
)
@Serializable
class SecurityEventWithData(
val id: Long?, val tag: Int, val level: Int?, val time: Long, val data: SecurityEventData?
)
@Serializable
sealed class SecurityEventData {
@Serializable
class AdbShellCmd(val command: String): SecurityEventData()
@Serializable
class AppProcessStart(
val name: String,
val time: Long,
val uid: Int,
val pid: Int,
val seinfo: String,
val hash: String
): SecurityEventData()
@Serializable
class BackupServiceToggled(
val admin: String,
val user: Int,
val state: Int
): SecurityEventData()
@Serializable
class BluetoothConnection(
val mac: String,
val successful: Int,
@SerialName("failure_reason") val failureReason: String
): SecurityEventData()
@Serializable
class BluetoothDisconnection(
val mac: String,
val reason: String
): SecurityEventData()
@Serializable
class CameraPolicySet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val disabled: Int
): SecurityEventData()
@Serializable
class CaInstalledRemoved(
val result: Int,
val subject: String,
val user: Int
): SecurityEventData()
@Serializable
class CertValidationFailure(val reason: String): SecurityEventData()
@Serializable
class CryptoSelfTestCompleted(val result: Int): SecurityEventData()
@Serializable
class KeyguardDisabledFeaturesSet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val mask: Int
): SecurityEventData()
@Serializable
class KeyguardDismissAuthAttempt(
val result: Int,
val strength: Int
): SecurityEventData()
@Serializable
class KeyGeneratedImportDestruction(
val result: Int,
val alias: String,
val uid: Int
): SecurityEventData()
@Serializable
class KeyIntegrityViolation(
val alias: String,
val uid: Int
): SecurityEventData()
@Serializable
class MaxPasswordAttemptsSet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val value: Int
): SecurityEventData()
@Serializable
class MaxScreenLockTimeoutSet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val timeout: Long
): SecurityEventData()
@Serializable
class MediaMountUnmount(
@SerialName("mount_point") val mountPoint: String,
val label: String
): SecurityEventData()
@Serializable
class OsStartup(
@SerialName("verified_boot_state") val verifiedBootState: String,
@SerialName("dm_verity_mode") val dmVerityMode: String
): SecurityEventData()
@Serializable
class PackageInstalledUninstalledUpdated(
val name: String,
val version: Long,
val user: Int
): SecurityEventData()
@Serializable
class PasswordChanged(
val complexity: Int,
val user: Int
): SecurityEventData()
@Serializable
class PasswordComplexityRequired(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val complexity: Int
): SecurityEventData()
@Serializable
class PasswordComplexitySet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val length: Int,
val quality: Int,
val letters: Int,
@SerialName("non_letters") val nonLetters: Int,
val digits: Int,
val uppercase: Int,
val lowercase: Int,
val symbols: Int
): SecurityEventData()
@Serializable
class PasswordExpirationSet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val expiration: Long
): SecurityEventData()
@Serializable
class PasswordHistoryLengthSet(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
val length: Int
): SecurityEventData()
@Serializable
class RemoteLock(
val admin: String,
@SerialName("admin_user") val adminUser: Int,
@SerialName("target_user") val targetUser: Int,
): SecurityEventData()
@Serializable
class SyncRecvSendFile(val path: String): SecurityEventData()
@Serializable
class UserRestrictionAddedRemoved(
val admin: String,
val user: Int,
val restriction: String
): SecurityEventData()
@Serializable
class WifiConnection(
val bssid: String,
val type: String,
@SerialName("failure_reason") val failureReason: String
): SecurityEventData()
@Serializable
class WifiDisconnection(
val bssid: String,
val reason: String
): SecurityEventData()
}
@RequiresApi(24)
fun parseSecurityEventData(event: SecurityLog.SecurityEvent): JsonElement? {
return when(event.tag) {
SecurityLog.TAG_ADB_SHELL_CMD -> JsonPrimitive(event.data as String)
fun transformSecurityEventData(tag: Int, payload: Any): SecurityEventData? {
return when(tag) {
SecurityLog.TAG_ADB_SHELL_CMD -> SecurityEventData.AdbShellCmd(payload as String)
SecurityLog.TAG_ADB_SHELL_INTERACTIVE -> null
SecurityLog.TAG_APP_PROCESS_START -> {
val payload = event.data as Array<*>
buildJsonObject {
put("name", payload[0] as String)
put("time", payload[1] as Long)
put("uid", payload[2] as Int)
put("pid", payload[3] as Int)
put("seinfo", payload[4] as String)
put("apk_hash", payload[5] as String)
}
val data = payload as Array<*>
SecurityEventData.AppProcessStart(
data[0] as String, data[1] as Long, data[2] as Int, data[3] as Int,
data[4] as String, data[5] as String
)
}
SecurityLog.TAG_BACKUP_SERVICE_TOGGLED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("state", payload[2] as Int)
}
val data = payload as Array<*>
SecurityEventData.BackupServiceToggled(data[0] as String, data[1] as Int, data[2] as Int)
}
SecurityLog.TAG_BLUETOOTH_CONNECTION -> {
val payload = event.data as Array<*>
buildJsonObject {
put("mac", payload[0] as String)
put("successful", payload[1] as Int)
(payload[2] as String).let { if(it != "") put("failure_reason", it) }
}
val data = payload as Array<*>
SecurityEventData.BluetoothConnection(data[0] as String, data[1] as Int, data[2] as String)
}
SecurityLog.TAG_BLUETOOTH_DISCONNECTION -> {
val payload = event.data as Array<*>
buildJsonObject {
put("mac", payload[0] as String)
(payload[1] as String).let { if(it != "") put("reason", it) }
}
val data = payload as Array<*>
SecurityEventData.BluetoothDisconnection(data[0] as String, data[1] as String)
}
SecurityLog.TAG_CAMERA_POLICY_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("disabled", payload[3] as Int)
}
val data = payload as Array<*>
SecurityEventData.CameraPolicySet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
)
}
SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, SecurityLog.TAG_CERT_AUTHORITY_REMOVED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("result", payload[0] as Int)
put("subject", payload[1] as String)
if(VERSION.SDK_INT >= 30) put("user", payload[2] as Int)
}
val data = payload as Array<*>
SecurityEventData.CaInstalledRemoved(data[0] as Int, data[1] as String, data[2] as Int)
}
SecurityLog.TAG_CERT_VALIDATION_FAILURE -> JsonPrimitive(event.data as String)
SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED -> JsonPrimitive(event.data as Int)
SecurityLog.TAG_CERT_VALIDATION_FAILURE ->
SecurityEventData.CertValidationFailure(payload as String)
SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED ->
SecurityEventData.CryptoSelfTestCompleted(payload as Int)
SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("mask", payload[3] as Int)
}
val data = payload as Array<*>
SecurityEventData.KeyguardDisabledFeaturesSet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
)
}
SecurityLog.TAG_KEYGUARD_DISMISSED -> null
SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT -> {
val payload = event.data as Array<*>
buildJsonObject {
put("result", payload[0] as Int)
put("strength", payload[1] as Int)
}
val data = payload as Array<*>
SecurityEventData.KeyguardDismissAuthAttempt(data[0] as Int, data[1] as Int)
}
SecurityLog.TAG_KEYGUARD_SECURED -> null
SecurityLog.TAG_KEY_DESTRUCTION, SecurityLog.TAG_KEY_GENERATED, SecurityLog.TAG_KEY_IMPORT -> {
val payload = event.data as Array<*>
buildJsonObject {
put("result", payload[0] as Int)
put("alias", payload[1] as String)
put("uid", payload[2] as Int)
}
}
SecurityLog.TAG_KEY_INTEGRITY_VIOLATION -> {
val payload = event.data as Array<*>
buildJsonObject {
put("alias", payload[0] as String)
put("uid", payload[1] as Int)
}
SecurityLog.TAG_KEY_GENERATED, SecurityLog.TAG_KEY_IMPORT, SecurityLog.TAG_KEY_DESTRUCTION -> {
val data = payload as Array<*>
SecurityEventData.KeyGeneratedImportDestruction(
data[0] as Int, data[1] as String, data[2] as Int
)
}
SecurityLog.TAG_LOGGING_STARTED, SecurityLog.TAG_LOGGING_STOPPED -> null
SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL -> null
SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("value", payload[3] as Int)
}
val data = payload as Array<*>
SecurityEventData.MaxPasswordAttemptsSet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
)
}
SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("timeout", payload[3] as Long)
}
val data = payload as Array<*>
SecurityEventData.MaxScreenLockTimeoutSet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Long
)
}
SecurityLog.TAG_MEDIA_MOUNT, SecurityLog.TAG_MEDIA_UNMOUNT -> {
val payload = event.data as Array<*>
buildJsonObject {
put("mount_point", payload[0] as String)
put("volume_label", payload[1] as String)
}
val data = payload as Array<*>
SecurityEventData.MediaMountUnmount(data[0] as String, data[1] as String)
}
SecurityLog.TAG_NFC_ENABLED, SecurityLog.TAG_NFC_DISABLED -> null
SecurityLog.TAG_OS_SHUTDOWN -> null
SecurityLog.TAG_OS_STARTUP -> {
val payload = event.data as Array<*>
buildJsonObject {
put("verified_boot_state", payload[0] as String)
put("dm_verify_state", payload[1] as String)
}
val data = payload as Array<*>
SecurityEventData.OsStartup(data[0] as String, data[1] as String)
}
SecurityLog.TAG_PACKAGE_INSTALLED, SecurityLog.TAG_PACKAGE_UNINSTALLED, SecurityLog.TAG_PACKAGE_UPDATED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("name", payload[0] as String)
put("version", payload[1] as Long)
put("user_id", payload[2] as Int)
}
SecurityLog.TAG_PACKAGE_INSTALLED, SecurityLog.TAG_PACKAGE_UPDATED,
SecurityLog.TAG_PACKAGE_UNINSTALLED -> {
val data = payload as Array<*>
SecurityEventData.PackageInstalledUninstalledUpdated(
data[0] as String, data[1] as Long, data[2] as Int
)
}
SecurityLog.TAG_PASSWORD_CHANGED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("complexity", payload[0] as Int)
put("user_id", payload[1] as Int)
}
val data = payload as Array<*>
SecurityEventData.PasswordChanged(data[0] as Int, data[1] as Int)
}
SecurityLog. TAG_PASSWORD_COMPLEXITY_REQUIRED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("complexity", payload[3] as Int)
}
SecurityLog.TAG_PASSWORD_COMPLEXITY_REQUIRED -> {
val data = payload as Array<*>
SecurityEventData.PasswordComplexityRequired(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
)
}
SecurityLog.TAG_PASSWORD_COMPLEXITY_SET -> {
val data = payload as Array<*>
SecurityEventData.PasswordComplexitySet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int, data[4] as Int,
data[5] as Int, data[6] as Int, data[7] as Int, data[8] as Int, data[9] as Int,
data[10] as Int
)
}
SecurityLog.TAG_PASSWORD_COMPLEXITY_SET -> null //Deprecated
SecurityLog.TAG_PASSWORD_EXPIRATION_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("timeout", payload[3] as Long)
}
val data = payload as Array<*>
SecurityEventData.PasswordExpirationSet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Long
)
}
SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
put("length", payload[3] as Int)
}
val data = payload as Array<*>
SecurityEventData.PasswordHistoryLengthSet(
data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
)
}
SecurityLog.TAG_REMOTE_LOCK -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
}
val data = payload as Array<*>
SecurityEventData.RemoteLock(data[0] as String, data[1] as Int, data[2] as Int)
}
SecurityLog.TAG_SYNC_RECV_FILE, SecurityLog.TAG_SYNC_SEND_FILE -> JsonPrimitive(event.data as String)
SecurityLog.TAG_SYNC_RECV_FILE, SecurityLog.TAG_SYNC_SEND_FILE ->
SecurityEventData.SyncRecvSendFile(payload as String)
SecurityLog.TAG_USER_RESTRICTION_ADDED, SecurityLog.TAG_USER_RESTRICTION_REMOVED -> {
val payload = event.data as Array<*>
buildJsonObject {
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("restriction", payload[2] as String)
}
val data = payload as Array<*>
SecurityEventData.UserRestrictionAddedRemoved(
data[0] as String, data[1] as Int, data[2] as String
)
}
SecurityLog.TAG_WIFI_CONNECTION -> {
val payload = event.data as Array<*>
buildJsonObject {
put("bssid", payload[0] as String)
put("type", payload[1] as String)
(payload[2] as String).let { if(it != "") put("failure_reason", it) }
}
val data = payload as Array<*>
SecurityEventData.WifiConnection(data[0] as String, data[1] as String, data[2] as String)
}
SecurityLog.TAG_WIFI_DISCONNECTION -> {
val payload = event.data as Array<*>
buildJsonObject {
put("bssid", payload[0] as String)
(payload[1] as String).let { if(it != "") put("reason", it) }
}
val data = payload as Array<*>
SecurityEventData.WifiDisconnection(data[0] as String, data[1] as String)
}
SecurityLog.TAG_WIPE_FAILURE -> null
else -> null
}
}
@RequiresApi(24)
fun retrieveSecurityLogs(app: MyApplication) {
CoroutineScope(Dispatchers.IO).launch {
val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR) ?: return@launch
app.myRepo.writeSecurityLogs(logs)
NotificationUtils.sendBasicNotification(
app, NotificationType.SecurityLogsCollected, MyNotificationChannel.SecurityLogging,
app.getString(R.string.n_logs_in_total, logs.size)
)
}
}
fun setDefaultAffiliationID() {
if (VERSION.SDK_INT < 26) return
if(!SP.isDefaultAffiliationIdSet) {

View File

@@ -1567,49 +1567,6 @@ fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
}
}
@Serializable object WifiAuthKeypair
@RequiresApi(31)
@Composable
fun WifiAuthKeypairScreen(onNavigateUp: () -> Unit) {
val context = LocalContext.current
val focusMgr = LocalFocusManager.current
var keyPair by remember { mutableStateOf("") }
MyScaffold(R.string.wifi_auth_keypair, onNavigateUp) {
OutlinedTextField(
value = keyPair,
label = { Text(stringResource(R.string.alias)) },
onValueChange = { keyPair = it },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(onDone = { focusMgr.clearFocus() }),
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.padding(vertical = 5.dp))
val isExist = try {
Privilege.DPM.isKeyPairGrantedToWifiAuth(keyPair)
} catch(e: java.lang.IllegalArgumentException) {
e.printStackTrace()
false
}
Text(stringResource(R.string.already_exist)+"$isExist")
Spacer(Modifier.padding(vertical = 5.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Button(
onClick = { context.showOperationResultToast(Privilege.DPM.grantKeyPairToWifiAuth(keyPair)) },
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.grant))
}
Button(
onClick = { context.showOperationResultToast(Privilege.DPM.revokeKeyPairFromWifiAuth(keyPair)) },
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.revoke))
}
}
}
}
@Serializable object PreferentialNetworkService
@RequiresApi(33)

View File

@@ -88,7 +88,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -111,11 +110,11 @@ import com.bintianqi.owndroid.MyViewModel
import com.bintianqi.owndroid.Privilege
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SP
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.formatDate
import com.bintianqi.owndroid.popToast
import com.bintianqi.owndroid.showOperationResultToast
import com.bintianqi.owndroid.ui.CheckBoxItem
import com.bintianqi.owndroid.ui.CircularProgressDialog
import com.bintianqi.owndroid.ui.ErrorDialog
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
@@ -132,7 +131,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable
import java.io.ByteArrayOutputStream
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import kotlin.math.roundToLong
@@ -178,7 +179,7 @@ fun SystemManagerScreen(
if(VERSION.SDK_INT >= 31) {
FunctionItem(R.string.nearby_streaming_policy, icon = R.drawable.share_fill0) { onNavigate(NearbyStreamingPolicy) }
}
if(VERSION.SDK_INT >= 28 && privilege.device) {
if (VERSION.SDK_INT >= 28 && privilege.device && !privilege.dhizuku) {
FunctionItem(R.string.lock_task_mode, icon = R.drawable.lock_fill0) { onNavigate(LockTaskMode) }
}
FunctionItem(R.string.ca_cert, icon = R.drawable.license_fill0) { onNavigate(CaCert) }
@@ -676,7 +677,7 @@ fun ChangeTimeZoneScreen(setTimeZone: (String) -> Boolean, onNavigateUp: () -> U
var dialog by remember { mutableStateOf(false) }
val availableIds = TimeZone.getAvailableIDs()
val validInput = inputTimezone in availableIds
MyScaffold(R.string.change_timezone, onNavigateUp) {
MyScaffold(R.string.change_timezone, onNavigateUp) {
OutlinedTextField(
value = inputTimezone,
label = { Text(stringResource(R.string.timezone_id)) },
@@ -1147,7 +1148,7 @@ fun NearbyStreamingPolicyScreen(
fun LockTaskModeScreen(
chosenPackage: Channel<String>, onChoosePackage: () -> Unit,
lockTaskPackages: StateFlow<List<AppInfo>>, getLockTaskPackages: () -> Unit,
setLockTaskPackage: (String, Boolean) -> Unit, startLockTaskMode: (String, String) -> Unit,
setLockTaskPackage: (String, Boolean) -> Unit, startLockTaskMode: (String, String) -> Boolean,
getLockTaskFeatures: () -> Int, setLockTaskFeature: (Int) -> String?, onNavigateUp: () -> Unit
) {
val coroutine = rememberCoroutineScope()
@@ -1202,9 +1203,10 @@ fun LockTaskModeScreen(
@RequiresApi(28)
@Composable
private fun StartLockTaskMode(
startLockTaskMode: (String, String) -> Unit,
startLockTaskMode: (String, String) -> Boolean,
chosenPackage: Channel<String>, onChoosePackage: () -> Unit
) {
val context = LocalContext.current
val focusMgr = LocalFocusManager.current
var packageName by rememberSaveable { mutableStateOf("") }
var activity by rememberSaveable { mutableStateOf("") }
@@ -1244,7 +1246,8 @@ private fun StartLockTaskMode(
.fillMaxWidth()
.padding(bottom = 5.dp),
onClick = {
startLockTaskMode(packageName, activity)
val result = startLockTaskMode(packageName, activity)
if (!result) context.showOperationResultToast(false)
},
enabled = packageName.isNotBlank() && (!specifyActivity || activity.isNotBlank())
) {
@@ -1525,77 +1528,104 @@ fun CaCertScreen(
@RequiresApi(24)
@Composable
fun SecurityLoggingScreen(onNavigateUp: () -> Unit) {
fun SecurityLoggingScreen(
getEnabled: () -> Boolean, setEnabled: (Boolean) -> Unit, exportLogs: (Uri, () -> Unit) -> Unit,
getCount: () -> Int, deleteLogs: () -> Unit, getPRLogs: () -> Boolean,
exportPRLogs: (Uri, () -> Unit) -> Unit, onNavigateUp: () -> Unit
) {
val context = LocalContext.current
val logFile = context.filesDir.resolve("SecurityLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) { fileSize = logFile.length() }
var preRebootSecurityLogs by remember { mutableStateOf(byteArrayOf()) }
val exportPreRebootSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
preRebootSecurityLogs.inputStream().copyTo(outStream)
var enabled by remember { mutableStateOf(false) }
var logsCount by remember { mutableIntStateOf(0) }
var exporting by remember { mutableStateOf(false) }
var dialog by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
enabled = getEnabled()
logsCount = getCount()
}
val exportLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/json")
) {
if (it != null) {
exporting = true
exportLogs(it) {
exporting = false
context.showOperationResultToast(true)
}
}
}
val exportSecurityLogs = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
outStream.write("[".toByteArray())
logFile.inputStream().use { it.copyTo(outStream) }
outStream.write("]".toByteArray())
context.showOperationResultToast(true)
val exportPRLogsLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.CreateDocument("application/json")
) {
if (it != null) {
exporting = true
exportPRLogs(it) {
exporting = false
context.showOperationResultToast(true)
}
}
}
MyScaffold(R.string.security_logging, onNavigateUp) {
MyScaffold(R.string.security_logging, onNavigateUp, 0.dp) {
SwitchItem(
R.string.enable,
getState = { Privilege.DPM.isSecurityLoggingEnabled(Privilege.DAR) },
onCheckedChange = { Privilege.DPM.setSecurityLoggingEnabled(Privilege.DAR, it) },
padding = false
R.string.enable, enabled, {
setEnabled(it)
enabled = it
}
)
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button(
onClick = {
exportSecurityLogs.launch("SecurityLogs.json")
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
) {
Text(stringResource(R.string.export_logs))
}
Button(
onClick = {
logFile.delete()
fileSize = logFile.length()
},
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.delete_logs))
}
Text(
stringResource(R.string.n_logs_in_total, logsCount),
Modifier.padding(HorizontalPadding)
)
Button(
{
val date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Date())
exportLauncher.launch("security_logs_$date")
},
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
logsCount > 0
) {
Text(stringResource(R.string.export_logs))
}
Notes(R.string.info_security_log)
Spacer(Modifier.padding(vertical = 5.dp))
if (logsCount > 0) FilledTonalButton(
{ dialog = true },
Modifier.fillMaxWidth().padding(HorizontalPadding, 4.dp)
) {
Text(stringResource(R.string.delete_logs))
}
Notes(R.string.info_security_log, HorizontalPadding)
Button(
onClick = {
val logs = Privilege.DPM.retrievePreRebootSecurityLogs(Privilege.DAR)
if(logs == null) {
context.popToast(R.string.no_logs)
return@Button
if (getPRLogs()) {
val date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Date())
exportPRLogsLauncher.launch("pre_reboot_security_logs_$date")
} else {
val outputStream = ByteArrayOutputStream()
outputStream.write("[".encodeToByteArray())
processSecurityLogs(logs, outputStream)
outputStream.write("]".encodeToByteArray())
preRebootSecurityLogs = outputStream.toByteArray()
exportPreRebootSecurityLogs.launch("PreRebootSecurityLogs.json")
context.showOperationResultToast(false)
}
},
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth().padding(HorizontalPadding, 15.dp)
) {
Text(stringResource(R.string.pre_reboot_security_logs))
}
Notes(R.string.info_pre_reboot_security_log)
Notes(R.string.info_pre_reboot_security_log, HorizontalPadding)
}
if (exporting) CircularProgressDialog { exporting = false }
if (dialog) AlertDialog(
text = { Text(stringResource(R.string.delete_logs)) },
confirmButton = {
TextButton({
deleteLogs()
logsCount = 0
dialog = false
}) {
Text(stringResource(R.string.confirm))
}
},
dismissButton = {
TextButton({ dialog = false }) {
Text(stringResource(R.string.cancel))
}
},
onDismissRequest = { dialog = false }
)
}
@Serializable object DisableAccountManagement