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

@@ -26,8 +26,8 @@ android {
applicationId = "com.bintianqi.owndroid"
minSdk = 21
targetSdk = 36
versionCode = 40
versionName = "7.1"
versionCode = 41
versionName = "7.2"
multiDexEnabled = false
}

View File

@@ -1,6 +1,7 @@
package com.bintianqi.owndroid
import android.Manifest
import android.content.pm.PackageManager
import android.os.Build.VERSION
import android.os.Bundle
import androidx.activity.compose.setContent
@@ -242,7 +243,10 @@ class MainActivity : FragmentActivity() {
val locale = context.resources?.configuration?.locale
zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
val vm by viewModels<MyViewModel>()
if (VERSION.SDK_INT >= 33) {
if (
VERSION.SDK_INT >= 33 &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
) {
val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
@@ -379,7 +383,11 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
CaCertScreen(vm.installedCaCerts, vm::getCaCerts, vm::installCaCert, vm::parseCaCert,
vm::exportCaCert, vm::uninstallCaCert, vm::uninstallAllCaCerts, ::navigateUp)
}
composable<SecurityLogging> { SecurityLoggingScreen(::navigateUp) }
composable<SecurityLogging> {
SecurityLoggingScreen(vm::getSecurityLoggingEnabled, vm::setSecurityLoggingEnabled,
vm::exportSecurityLogs, vm::getSecurityLogsCount, vm::deleteSecurityLogs,
vm::getPreRebootSecurityLogs, vm::exportPreRebootSecurityLogs, ::navigateUp)
}
composable<DisableAccountManagement> {
DisableAccountManagementScreen(vm.mdAccountTypes, vm::getMdAccountTypes,
vm::setMdAccountType, ::navigateUp)

View File

@@ -4,12 +4,15 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 1) {
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 2) {
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
"signature TEXT, permissions TEXT)")
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion < 2) {
db.execSQL("CREATE TABLE security_logs (id INTEGER, tag INTEGER, level INTEGER," +
"time INTEGER, data TEXT)")
}
}
}

View File

@@ -1,7 +1,18 @@
package com.bintianqi.owndroid
import android.app.admin.SecurityLog
import android.content.ContentValues
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase
import android.os.Build.VERSION
import androidx.annotation.RequiresApi
import androidx.core.database.getStringOrNull
import com.bintianqi.owndroid.dpm.SecurityEvent
import com.bintianqi.owndroid.dpm.SecurityEventWithData
import com.bintianqi.owndroid.dpm.transformSecurityEventData
import kotlinx.serialization.json.ClassDiscriminatorMode
import kotlinx.serialization.json.Json
import java.io.OutputStream
class MyRepository(val dbHelper: MyDbHelper) {
fun getDhizukuClients(): List<DhizukuClientInfo> {
@@ -43,4 +54,104 @@ class MyRepository(val dbHelper: MyDbHelper) {
fun deleteDhizukuClient(info: DhizukuClientInfo) {
dbHelper.writableDatabase.delete("dhizuku_clients", "uid = ${info.uid}", null)
}
fun getSecurityLogsCount(): Long {
return DatabaseUtils.queryNumEntries(dbHelper.readableDatabase, "security_logs")
}
@RequiresApi(24)
fun writeSecurityLogs(events: List<SecurityLog.SecurityEvent>) {
val db = dbHelper.writableDatabase
val json = Json {
classDiscriminatorMode = ClassDiscriminatorMode.NONE
}
val statement = db.compileStatement("INSERT INTO security_logs VALUES (?, ?, ?, ?, ?)")
db.beginTransaction()
events.forEach { event ->
try {
if (VERSION.SDK_INT >= 28) {
statement.bindLong(1, event.id)
statement.bindLong(3, event.logLevel.toLong())
} else {
statement.bindNull(1)
statement.bindNull(3)
}
statement.bindLong(2, event.tag.toLong())
statement.bindLong(4, event.timeNanos / 1000000)
val dataObject = transformSecurityEventData(event.tag, event.data)
if (dataObject == null) {
statement.bindNull(5)
} else {
statement.bindString(5, json.encodeToString(dataObject))
}
statement.executeInsert()
} catch (e: Exception) {
e.printStackTrace()
} finally {
statement.clearBindings()
}
}
db.setTransactionSuccessful()
db.endTransaction()
statement.close()
}
fun exportSecurityLogs(stream: OutputStream) {
var offset = 0
val json = Json {
explicitNulls = false
}
var addComma = false
val bw = stream.bufferedWriter()
bw.write("[")
while (true) {
dbHelper.readableDatabase.rawQuery(
"SELECT * FROM security_logs LIMIT ? OFFSET ?",
arrayOf(100.toString(), offset.toString())
).use { cursor ->
if (cursor.count == 0) {
break
}
while (cursor.moveToNext()) {
if (addComma) bw.write(",")
addComma = true
val event = SecurityEvent(
cursor.getLong(0), cursor.getInt(1), cursor.getInt(2), cursor.getLong(3),
cursor.getStringOrNull(4)?.let { json.decodeFromString(it) }
)
bw.write(json.encodeToString(event))
}
offset += 100
}
}
bw.write("]")
bw.close()
}
@RequiresApi(24)
fun exportPRSecurityLogs(logs: List<SecurityLog.SecurityEvent>, stream: OutputStream) {
val bw = stream.bufferedWriter()
bw.write("[")
val json = Json {
explicitNulls = false
classDiscriminatorMode = ClassDiscriminatorMode.NONE
}
var addComma = false
logs.forEach { log ->
try {
if (addComma) bw.write(",")
addComma = true
val event = SecurityEventWithData(
if (VERSION.SDK_INT >= 28) log.id else null, log.tag,
if (VERSION.SDK_INT >= 28) log.logLevel else null, log.timeNanos / 1000000,
transformSecurityEventData(log.tag, log.data)
)
bw.write(json.encodeToString(event))
} catch (e: Exception) {
e.printStackTrace()
}
}
bw.write("]")
bw.close()
}
fun deleteSecurityLogs() {
dbHelper.writableDatabase.execSQL("DELETE FROM security_logs")
}
}

View File

@@ -13,6 +13,7 @@ import android.app.admin.FactoryResetProtectionPolicy
import android.app.admin.IDevicePolicyManager
import android.app.admin.PackagePolicy
import android.app.admin.PreferentialNetworkServiceConfig
import android.app.admin.SecurityLog
import android.app.admin.SystemUpdateInfo
import android.app.admin.SystemUpdatePolicy
import android.app.admin.WifiSsidPolicy
@@ -756,9 +757,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
getLockTaskPackages()
}
@RequiresApi(28)
fun startLockTaskMode(packageName: String, activity: String): Int {
if (!NotificationUtils.checkPermission(application)) return 0
if (!DPM.isLockTaskPermitted(packageName)) return 1
fun startLockTaskMode(packageName: String, activity: String): Boolean {
if (!DPM.isLockTaskPermitted(packageName)) {
val list = lockTaskPackages.value.map { it.name } + packageName
DPM.setLockTaskPackages(DAR, list.toTypedArray())
getLockTaskPackages()
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val intent = if(activity.isNotEmpty()) {
Intent().setComponent(ComponentName(packageName, activity))
@@ -766,9 +770,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
application.startActivity(intent, options.toBundle())
return 0
return true
} else {
return 2
return false
}
}
@RequiresApi(28)
@@ -913,6 +917,53 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
}
DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback)
}
@RequiresApi(24)
fun getSecurityLoggingEnabled(): Boolean {
return DPM.isSecurityLoggingEnabled(DAR)
}
@RequiresApi(24)
fun setSecurityLoggingEnabled(enabled: Boolean) {
DPM.setSecurityLoggingEnabled(DAR, enabled)
}
fun exportSecurityLogs(uri: Uri, callback: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
application.contentResolver.openOutputStream(uri)?.use {
myRepo.exportSecurityLogs(it)
}
withContext(Dispatchers.Main) {
callback()
}
}
}
fun getSecurityLogsCount(): Int {
return myRepo.getSecurityLogsCount().toInt()
}
fun deleteSecurityLogs() {
myRepo.deleteSecurityLogs()
}
var preRebootSecurityLogs = emptyList<SecurityLog.SecurityEvent>()
@RequiresApi(24)
fun getPreRebootSecurityLogs(): Boolean {
if (preRebootSecurityLogs.isNotEmpty()) return true
return try {
val logs = DPM.retrievePreRebootSecurityLogs(DAR)
if (logs != null && logs.isNotEmpty()) {
preRebootSecurityLogs = logs
true
} else false
} catch (_: SecurityException) {
false
}
}
@RequiresApi(24)
fun exportPreRebootSecurityLogs(uri: Uri, callback: () -> Unit) {
viewModelScope.launch(Dispatchers.IO) {
val stream = application.contentResolver.openOutputStream(uri) ?: return@launch
myRepo.exportPRSecurityLogs(preRebootSecurityLogs, stream)
stream.close()
withContext(Dispatchers.Main) { callback() }
}
}
@RequiresApi(24)
fun isCreatingWorkProfileAllowed(): Boolean {

View File

@@ -1,48 +1,35 @@
package com.bintianqi.owndroid
import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.NotificationChannelCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
object NotificationUtils {
fun checkPermission(context: Context): Boolean {
return if(Build.VERSION.SDK_INT >= 33)
context.checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
else false
}
fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < 26) return
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val lockTaskMode = NotificationChannel(
MyNotificationChannel.LockTaskMode.id,
context.getString(MyNotificationChannel.LockTaskMode.text),
NotificationManager.IMPORTANCE_HIGH
)
val events = NotificationChannel(
MyNotificationChannel.Events.id,
context.getString(MyNotificationChannel.Events.text),
NotificationManager.IMPORTANCE_HIGH
)
nm.createNotificationChannels(listOf(lockTaskMode, events))
val channels = MyNotificationChannel.entries.map {
NotificationChannelCompat.Builder(it.id, it.importance)
.setName(context.getString(it.text))
.build()
}
NotificationManagerCompat.from(context).createNotificationChannelsCompat(channels)
}
fun notifyEvent(context: Context, type: NotificationType, text: String) {
val notification = NotificationCompat.Builder(context, MyNotificationChannel.Events.id)
fun sendBasicNotification(
context: Context, type: NotificationType, channel: MyNotificationChannel, text: String
) {
val notification = NotificationCompat.Builder(context, channel.id)
.setSmallIcon(type.icon)
.setContentTitle(context.getString(type.text))
.setContentText(text)
.build()
notify(context, type, notification)
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(type.id, notification)
}
fun notify(context: Context, type: NotificationType, notification: Notification) {
fun notifyEvent(context: Context, type: NotificationType, text: String) {
val enabledNotifications = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
if (enabledNotifications == null || type.id in enabledNotifications) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(type.id, notification)
sendBasicNotification(context, type, MyNotificationChannel.Events, text)
}
}
fun cancel(context: Context, type: NotificationType) {
@@ -62,10 +49,12 @@ enum class NotificationType(val id: Int, val text: Int, val icon: Int) {
BugReportShared(8, R.string.bug_report_shared, R.drawable.bug_report_fill0),
BugReportSharingDeclined(9, R.string.bug_report_sharing_declined, R.drawable.bug_report_fill0),
BugReportFailed(10, R.string.bug_report_failed, R.drawable.bug_report_fill0),
SystemUpdatePending(11, R.string.system_update_pending, R.drawable.system_update_fill0)
SystemUpdatePending(11, R.string.system_update_pending, R.drawable.system_update_fill0),
SecurityLogsCollected(12, R.string.security_logs_collected, R.drawable.description_fill0),
}
enum class MyNotificationChannel(val id: String, val text: Int) {
LockTaskMode("LockTaskMode", R.string.lock_task_mode),
Events("Events", R.string.events)
enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) {
LockTaskMode("LockTaskMode", R.string.lock_task_mode, NotificationManagerCompat.IMPORTANCE_HIGH),
Events("Events", R.string.events, NotificationManagerCompat.IMPORTANCE_LOW),
SecurityLogging("SecurityLogging", R.string.security_logging, NotificationManagerCompat.IMPORTANCE_MIN)
}

View File

@@ -1,5 +1,6 @@
package com.bintianqi.owndroid
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.admin.DeviceAdminReceiver
import android.content.ComponentName
@@ -12,7 +13,7 @@ import android.os.UserManager
import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
import com.bintianqi.owndroid.dpm.processSecurityLogs
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -21,11 +22,10 @@ class Receiver : DeviceAdminReceiver() {
override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent)
if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") {
val dpm = getManager(context)
val receiver = ComponentName(context, this::class.java)
val packages = dpm.getLockTaskPackages(receiver)
dpm.setLockTaskPackages(receiver, arrayOf())
dpm.setLockTaskPackages(receiver, packages)
val packages = Privilege.DPM.getLockTaskPackages(receiver)
Privilege.DPM.setLockTaskPackages(receiver, arrayOf())
Privilege.DPM.setLockTaskPackages(receiver, packages)
}
}
@@ -56,29 +56,23 @@ class Receiver : DeviceAdminReceiver() {
override fun onSecurityLogsAvailable(context: Context, intent: Intent) {
super.onSecurityLogsAvailable(context, intent)
if(VERSION.SDK_INT >= 24) {
CoroutineScope(Dispatchers.IO).launch {
val events = getManager(context).retrieveSecurityLogs(MyAdminComponent) ?: return@launch
val file = context.filesDir.resolve("SecurityLogs.json")
val fileExists = file.exists()
file.outputStream().use {
if(fileExists) it.write(",".encodeToByteArray())
processSecurityLogs(events, it)
}
}
if (VERSION.SDK_INT >= 24) {
retrieveSecurityLogs(context.applicationContext as MyApplication)
}
}
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
super.onLockTaskModeEntering(context, intent, pkg)
if (!NotificationUtils.checkPermission(context)) return
val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id)
val stopIntent = Intent(context, this::class.java)
.setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
val pendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id)
.setContentTitle(context.getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build())
NotificationUtils.notify(context, NotificationType.LockTaskMode, builder.build())
.build()
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(NotificationType.LockTaskMode.id, notification)
}
override fun onLockTaskModeExiting(context: Context, intent: Intent) {

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

View File

@@ -35,7 +35,6 @@
<string name="time_unit_ms">Время (мс)</string>
<string name="length">Длина</string>
<string name="none">Нет</string>
<string name="no_logs">Нет журналов</string>
<string name="default_stringres">По умолчанию</string>
<string name="apply">Применить</string>
<string name="decide_by_user">Определять пользователем</string>

View File

@@ -35,7 +35,6 @@
<string name="time_unit_ms">Zaman (ms)</string>
<string name="length">Uzunluk</string>
<string name="none">Yok</string>
<string name="no_logs">Kayıt Yok</string>
<string name="default_stringres">Varsayılan</string>
<string name="apply">Uygula</string>
<string name="decide_by_user">Kullanıcı Tarafından Karar Ver</string>

View File

@@ -35,7 +35,6 @@
<string name="time_unit_ms">时间(毫秒)</string>
<string name="length">长度</string>
<string name="none"></string>
<string name="no_logs">无日志</string>
<string name="default_stringres">默认</string>
<string name="apply">应用</string>
<string name="decide_by_user">由用户决定</string>
@@ -194,6 +193,8 @@
<string name="uninstall_all_user_ca_cert">卸载所有用户证书</string>
<string name="security_logging">安全日志</string>
<string name="pre_reboot_security_logs">重启前安全日志</string>
<string name="security_logs_collected">安全日志已收集</string>
<string name="n_logs_in_total">总共 %1$d 条日志</string>
<string name="wipe_data">清除数据</string>
<string name="wipe_external_storage">清除外部存储</string>
<string name="wipe_reset_protection_data">清除受保护的数据</string>

View File

@@ -37,7 +37,6 @@
<string name="time_unit_ms">Time(ms)</string>
<string name="length">Length</string>
<string name="none">None</string>
<string name="no_logs">No logs</string>
<string name="default_stringres">Default</string>
<string name="apply">Apply</string>
<string name="decide_by_user">Decide by user</string>
@@ -221,6 +220,8 @@
<string name="uninstall_all_user_ca_cert">Uninstall all user CA certificate</string>
<string name="security_logging">Security logging</string>
<string name="pre_reboot_security_logs">Pre-reboot security logs</string>
<string name="security_logs_collected">Security logs collected</string>
<string name="n_logs_in_total">%1$d logs in total</string>
<string name="wipe_data">Wipe data</string>
<string name="wipe_external_storage">Wipe external storage</string>
<string name="wipe_reset_protection_data">Wipe protected data</string>