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" applicationId = "com.bintianqi.owndroid"
minSdk = 21 minSdk = 21
targetSdk = 36 targetSdk = 36
versionCode = 40 versionCode = 41
versionName = "7.1" versionName = "7.2"
multiDexEnabled = false multiDexEnabled = false
} }

View File

@@ -1,6 +1,7 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.Manifest import android.Manifest
import android.content.pm.PackageManager
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
@@ -242,7 +243,10 @@ class MainActivity : FragmentActivity() {
val locale = context.resources?.configuration?.locale val locale = context.resources?.configuration?.locale
zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA zhCN = locale == Locale.SIMPLIFIED_CHINESE || locale == Locale.CHINESE || locale == Locale.CHINA
val vm by viewModels<MyViewModel>() 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()) {} val launcher = registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
launcher.launch(Manifest.permission.POST_NOTIFICATIONS) 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, CaCertScreen(vm.installedCaCerts, vm::getCaCerts, vm::installCaCert, vm::parseCaCert,
vm::exportCaCert, vm::uninstallCaCert, vm::uninstallAllCaCerts, ::navigateUp) 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> { composable<DisableAccountManagement> {
DisableAccountManagementScreen(vm.mdAccountTypes, vm::getMdAccountTypes, DisableAccountManagementScreen(vm.mdAccountTypes, vm::getMdAccountTypes,
vm::setMdAccountType, ::navigateUp) vm::setMdAccountType, ::navigateUp)

View File

@@ -4,12 +4,15 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper 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) { override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," + db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
"signature TEXT, permissions TEXT)") "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 package com.bintianqi.owndroid
import android.app.admin.SecurityLog
import android.content.ContentValues import android.content.ContentValues
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase 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) { class MyRepository(val dbHelper: MyDbHelper) {
fun getDhizukuClients(): List<DhizukuClientInfo> { fun getDhizukuClients(): List<DhizukuClientInfo> {
@@ -43,4 +54,104 @@ class MyRepository(val dbHelper: MyDbHelper) {
fun deleteDhizukuClient(info: DhizukuClientInfo) { fun deleteDhizukuClient(info: DhizukuClientInfo) {
dbHelper.writableDatabase.delete("dhizuku_clients", "uid = ${info.uid}", null) 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.IDevicePolicyManager
import android.app.admin.PackagePolicy import android.app.admin.PackagePolicy
import android.app.admin.PreferentialNetworkServiceConfig import android.app.admin.PreferentialNetworkServiceConfig
import android.app.admin.SecurityLog
import android.app.admin.SystemUpdateInfo import android.app.admin.SystemUpdateInfo
import android.app.admin.SystemUpdatePolicy import android.app.admin.SystemUpdatePolicy
import android.app.admin.WifiSsidPolicy import android.app.admin.WifiSsidPolicy
@@ -756,9 +757,12 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
getLockTaskPackages() getLockTaskPackages()
} }
@RequiresApi(28) @RequiresApi(28)
fun startLockTaskMode(packageName: String, activity: String): Int { fun startLockTaskMode(packageName: String, activity: String): Boolean {
if (!NotificationUtils.checkPermission(application)) return 0 if (!DPM.isLockTaskPermitted(packageName)) {
if (!DPM.isLockTaskPermitted(packageName)) return 1 val list = lockTaskPackages.value.map { it.name } + packageName
DPM.setLockTaskPackages(DAR, list.toTypedArray())
getLockTaskPackages()
}
val options = ActivityOptions.makeBasic().setLockTaskEnabled(true) val options = ActivityOptions.makeBasic().setLockTaskEnabled(true)
val intent = if(activity.isNotEmpty()) { val intent = if(activity.isNotEmpty()) {
Intent().setComponent(ComponentName(packageName, activity)) Intent().setComponent(ComponentName(packageName, activity))
@@ -766,9 +770,9 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
if (intent != null) { if (intent != null) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
application.startActivity(intent, options.toBundle()) application.startActivity(intent, options.toBundle())
return 0 return true
} else { } else {
return 2 return false
} }
} }
@RequiresApi(28) @RequiresApi(28)
@@ -913,6 +917,53 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
} }
DPM.installSystemUpdate(DAR, uri, application.mainExecutor, callback) 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) @RequiresApi(24)
fun isCreatingWorkProfileAllowed(): Boolean { fun isCreatingWorkProfileAllowed(): Boolean {

View File

@@ -1,49 +1,36 @@
package com.bintianqi.owndroid package com.bintianqi.owndroid
import android.Manifest
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import androidx.core.app.NotificationChannelCompat
import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
object NotificationUtils { 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) { fun createChannels(context: Context) {
if (Build.VERSION.SDK_INT < 26) return val channels = MyNotificationChannel.entries.map {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager NotificationChannelCompat.Builder(it.id, it.importance)
val lockTaskMode = NotificationChannel( .setName(context.getString(it.text))
MyNotificationChannel.LockTaskMode.id, .build()
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))
} }
fun notifyEvent(context: Context, type: NotificationType, text: String) { NotificationManagerCompat.from(context).createNotificationChannelsCompat(channels)
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) .setSmallIcon(type.icon)
.setContentTitle(context.getString(type.text)) .setContentTitle(context.getString(type.text))
.setContentText(text) .setContentText(text)
.build() .build()
notify(context, type, notification)
}
fun notify(context: Context, type: NotificationType, notification: Notification) {
val enabledNotifications = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
if (enabledNotifications == null || type.id in enabledNotifications) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nm.notify(type.id, notification) nm.notify(type.id, notification)
} }
fun notifyEvent(context: Context, type: NotificationType, text: String) {
val enabledNotifications = SP.notifications?.split(',')?.mapNotNull { it.toIntOrNull() }
if (enabledNotifications == null || type.id in enabledNotifications) {
sendBasicNotification(context, type, MyNotificationChannel.Events, text)
}
} }
fun cancel(context: Context, type: NotificationType) { fun cancel(context: Context, type: NotificationType) {
val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val nm = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
@@ -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), 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), 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), 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) { enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) {
LockTaskMode("LockTaskMode", R.string.lock_task_mode), LockTaskMode("LockTaskMode", R.string.lock_task_mode, NotificationManagerCompat.IMPORTANCE_HIGH),
Events("Events", R.string.events) 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 package com.bintianqi.owndroid
import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.admin.DeviceAdminReceiver import android.app.admin.DeviceAdminReceiver
import android.content.ComponentName import android.content.ComponentName
@@ -12,7 +13,7 @@ import android.os.UserManager
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.bintianqi.owndroid.dpm.handleNetworkLogs import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handlePrivilegeChange 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.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -21,11 +22,10 @@ class Receiver : DeviceAdminReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
super.onReceive(context, intent) super.onReceive(context, intent)
if(VERSION.SDK_INT >= 26 && intent.action == "com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") { 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 receiver = ComponentName(context, this::class.java)
val packages = dpm.getLockTaskPackages(receiver) val packages = Privilege.DPM.getLockTaskPackages(receiver)
dpm.setLockTaskPackages(receiver, arrayOf()) Privilege.DPM.setLockTaskPackages(receiver, arrayOf())
dpm.setLockTaskPackages(receiver, packages) Privilege.DPM.setLockTaskPackages(receiver, packages)
} }
} }
@@ -56,29 +56,23 @@ class Receiver : DeviceAdminReceiver() {
override fun onSecurityLogsAvailable(context: Context, intent: Intent) { override fun onSecurityLogsAvailable(context: Context, intent: Intent) {
super.onSecurityLogsAvailable(context, intent) super.onSecurityLogsAvailable(context, intent)
if(VERSION.SDK_INT >= 24) { if (VERSION.SDK_INT >= 24) {
CoroutineScope(Dispatchers.IO).launch { retrieveSecurityLogs(context.applicationContext as MyApplication)
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)
}
}
} }
} }
override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) { override fun onLockTaskModeEntering(context: Context, intent: Intent, pkg: String) {
super.onLockTaskModeEntering(context, intent, pkg) super.onLockTaskModeEntering(context, intent, pkg)
if (!NotificationUtils.checkPermission(context)) return val stopIntent = Intent(context, this::class.java)
val intent = Intent(context, this::class.java).setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE") .setAction("com.bintianqi.owndroid.action.STOP_LOCK_TASK_MODE")
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE) val pendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id) val notification = NotificationCompat.Builder(context, MyNotificationChannel.LockTaskMode.id)
.setContentTitle(context.getText(R.string.lock_task_mode)) .setContentTitle(context.getText(R.string.lock_task_mode))
.setSmallIcon(R.drawable.lock_fill0) .setSmallIcon(R.drawable.lock_fill0)
.addAction(NotificationCompat.Action.Builder(null, context.getString(R.string.stop), pendingIntent).build()) .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) { 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.os.Build.VERSION
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi 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.Privilege
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.SP import com.bintianqi.owndroid.SP
import com.bintianqi.owndroid.ShortcutUtils import com.bintianqi.owndroid.ShortcutUtils
import com.rosan.dhizuku.api.Dhizuku import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.DhizukuBinderWrapper import com.rosan.dhizuku.api.DhizukuBinderWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow 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.Json
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.add import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonArray import kotlinx.serialization.json.putJsonArray
import java.io.OutputStream
@SuppressLint("PrivateApi") @SuppressLint("PrivateApi")
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? { fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
@@ -166,230 +173,336 @@ fun handleNetworkLogs(context: Context, batchToken: Long) {
buffer.close() buffer.close()
} }
@RequiresApi(24) @Serializable
fun processSecurityLogs(securityEvents: List<SecurityLog.SecurityEvent>, outputStream: OutputStream) { class SecurityEvent(
val json = Json { ignoreUnknownKeys = true; explicitNulls = false } val id: Long?, val tag: Int, val level: Int?, val time: Long, val data: JsonObject?
val buffer = outputStream.bufferedWriter() )
securityEvents.forEachIndexed { index, event ->
val item = buildJsonObject { @Serializable
put("time", event.timeNanos / 1000) class SecurityEventWithData(
put("tag", event.tag) val id: Long?, val tag: Int, val level: Int?, val time: Long, val data: SecurityEventData?
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) } @Serializable
} sealed class SecurityEventData {
buffer.write(json.encodeToString(item)) @Serializable
if(index < securityEvents.size - 1) buffer.write(",") class AdbShellCmd(val command: String): SecurityEventData()
} @Serializable
buffer.close() 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 transformSecurityEventData(tag: Int, payload: Any): SecurityEventData? {
fun parseSecurityEventData(event: SecurityLog.SecurityEvent): JsonElement? { return when(tag) {
return when(event.tag) { SecurityLog.TAG_ADB_SHELL_CMD -> SecurityEventData.AdbShellCmd(payload as String)
SecurityLog.TAG_ADB_SHELL_CMD -> JsonPrimitive(event.data as String)
SecurityLog.TAG_ADB_SHELL_INTERACTIVE -> null SecurityLog.TAG_ADB_SHELL_INTERACTIVE -> null
SecurityLog.TAG_APP_PROCESS_START -> { SecurityLog.TAG_APP_PROCESS_START -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.AppProcessStart(
put("name", payload[0] as String) data[0] as String, data[1] as Long, data[2] as Int, data[3] as Int,
put("time", payload[1] as Long) data[4] as String, data[5] as String
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)
}
} }
SecurityLog.TAG_BACKUP_SERVICE_TOGGLED -> { SecurityLog.TAG_BACKUP_SERVICE_TOGGLED -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.BackupServiceToggled(data[0] as String, data[1] as Int, data[2] as Int)
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("state", payload[2] as Int)
}
} }
SecurityLog.TAG_BLUETOOTH_CONNECTION -> { SecurityLog.TAG_BLUETOOTH_CONNECTION -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.BluetoothConnection(data[0] as String, data[1] as Int, data[2] as String)
put("mac", payload[0] as String)
put("successful", payload[1] as Int)
(payload[2] as String).let { if(it != "") put("failure_reason", it) }
}
} }
SecurityLog.TAG_BLUETOOTH_DISCONNECTION -> { SecurityLog.TAG_BLUETOOTH_DISCONNECTION -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.BluetoothDisconnection(data[0] as String, data[1] as String)
put("mac", payload[0] as String)
(payload[1] as String).let { if(it != "") put("reason", it) }
}
} }
SecurityLog.TAG_CAMERA_POLICY_SET -> { SecurityLog.TAG_CAMERA_POLICY_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.CameraPolicySet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("disabled", payload[3] as Int)
}
} }
SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, SecurityLog.TAG_CERT_AUTHORITY_REMOVED -> { SecurityLog.TAG_CERT_AUTHORITY_INSTALLED, SecurityLog.TAG_CERT_AUTHORITY_REMOVED -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.CaInstalledRemoved(data[0] as Int, data[1] as String, data[2] as Int)
put("result", payload[0] as Int)
put("subject", payload[1] as String)
if(VERSION.SDK_INT >= 30) put("user", payload[2] as Int)
} }
} SecurityLog.TAG_CERT_VALIDATION_FAILURE ->
SecurityLog.TAG_CERT_VALIDATION_FAILURE -> JsonPrimitive(event.data as String) SecurityEventData.CertValidationFailure(payload as String)
SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED -> JsonPrimitive(event.data as Int) SecurityLog.TAG_CRYPTO_SELF_TEST_COMPLETED ->
SecurityEventData.CryptoSelfTestCompleted(payload as Int)
SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET -> { SecurityLog.TAG_KEYGUARD_DISABLED_FEATURES_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.KeyguardDisabledFeaturesSet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("mask", payload[3] as Int)
}
} }
SecurityLog.TAG_KEYGUARD_DISMISSED -> null SecurityLog.TAG_KEYGUARD_DISMISSED -> null
SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT -> { SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.KeyguardDismissAuthAttempt(data[0] as Int, data[1] as Int)
put("result", payload[0] as Int)
put("strength", payload[1] as Int)
}
} }
SecurityLog.TAG_KEYGUARD_SECURED -> null SecurityLog.TAG_KEYGUARD_SECURED -> null
SecurityLog.TAG_KEY_DESTRUCTION, SecurityLog.TAG_KEY_GENERATED, SecurityLog.TAG_KEY_IMPORT -> { SecurityLog.TAG_KEY_GENERATED, SecurityLog.TAG_KEY_IMPORT, SecurityLog.TAG_KEY_DESTRUCTION -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.KeyGeneratedImportDestruction(
put("result", payload[0] as Int) data[0] as Int, data[1] as String, data[2] 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_LOGGING_STARTED, SecurityLog.TAG_LOGGING_STOPPED -> null SecurityLog.TAG_LOGGING_STARTED, SecurityLog.TAG_LOGGING_STOPPED -> null
SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL -> null SecurityLog.TAG_LOG_BUFFER_SIZE_CRITICAL -> null
SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET -> { SecurityLog.TAG_MAX_PASSWORD_ATTEMPTS_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.MaxPasswordAttemptsSet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("value", payload[3] as Int)
}
} }
SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET -> { SecurityLog.TAG_MAX_SCREEN_LOCK_TIMEOUT_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.MaxScreenLockTimeoutSet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Long
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("timeout", payload[3] as Long)
}
} }
SecurityLog.TAG_MEDIA_MOUNT, SecurityLog.TAG_MEDIA_UNMOUNT -> { SecurityLog.TAG_MEDIA_MOUNT, SecurityLog.TAG_MEDIA_UNMOUNT -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.MediaMountUnmount(data[0] as String, data[1] as String)
put("mount_point", payload[0] as String)
put("volume_label", payload[1] as String)
}
} }
SecurityLog.TAG_NFC_ENABLED, SecurityLog.TAG_NFC_DISABLED -> null
SecurityLog.TAG_OS_SHUTDOWN -> null SecurityLog.TAG_OS_SHUTDOWN -> null
SecurityLog.TAG_OS_STARTUP -> { SecurityLog.TAG_OS_STARTUP -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.OsStartup(data[0] as String, data[1] as String)
put("verified_boot_state", payload[0] as String)
put("dm_verify_state", payload[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 -> { SecurityLog.TAG_PASSWORD_CHANGED -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.PasswordChanged(data[0] as Int, data[1] as Int)
put("complexity", payload[0] as Int)
put("user_id", payload[1] 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_REQUIRED -> { SecurityLog.TAG_PASSWORD_COMPLEXITY_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.PasswordComplexitySet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int, data[4] as Int,
put("admin_user_id", payload[1] as Int) data[5] as Int, data[6] as Int, data[7] as Int, data[8] as Int, data[9] as Int,
put("target_user_id", payload[2] as Int) data[10] as Int
put("complexity", payload[3] as Int) )
} }
}
SecurityLog.TAG_PASSWORD_COMPLEXITY_SET -> null //Deprecated
SecurityLog.TAG_PASSWORD_EXPIRATION_SET -> { SecurityLog.TAG_PASSWORD_EXPIRATION_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.PasswordExpirationSet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Long
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("timeout", payload[3] as Long)
}
} }
SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET -> { SecurityLog.TAG_PASSWORD_HISTORY_LENGTH_SET -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.PasswordHistoryLengthSet(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as Int, data[3] as Int
put("admin_user_id", payload[1] as Int) )
put("target_user_id", payload[2] as Int)
put("length", payload[3] as Int)
}
} }
SecurityLog.TAG_REMOTE_LOCK -> { SecurityLog.TAG_REMOTE_LOCK -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.RemoteLock(data[0] as String, data[1] as Int, data[2] as Int)
put("admin", payload[0] as String)
put("admin_user_id", payload[1] as Int)
put("target_user_id", payload[2] as Int)
} }
} SecurityLog.TAG_SYNC_RECV_FILE, SecurityLog.TAG_SYNC_SEND_FILE ->
SecurityLog.TAG_SYNC_RECV_FILE, SecurityLog.TAG_SYNC_SEND_FILE -> JsonPrimitive(event.data as String) SecurityEventData.SyncRecvSendFile(payload as String)
SecurityLog.TAG_USER_RESTRICTION_ADDED, SecurityLog.TAG_USER_RESTRICTION_REMOVED -> { SecurityLog.TAG_USER_RESTRICTION_ADDED, SecurityLog.TAG_USER_RESTRICTION_REMOVED -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.UserRestrictionAddedRemoved(
put("admin", payload[0] as String) data[0] as String, data[1] as Int, data[2] as String
put("admin_user_id", payload[1] as Int) )
put("restriction", payload[2] as String)
}
} }
SecurityLog.TAG_WIFI_CONNECTION -> { SecurityLog.TAG_WIFI_CONNECTION -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.WifiConnection(data[0] as String, data[1] as String, data[2] as String)
put("bssid", payload[0] as String)
put("type", payload[1] as String)
(payload[2] as String).let { if(it != "") put("failure_reason", it) }
}
} }
SecurityLog.TAG_WIFI_DISCONNECTION -> { SecurityLog.TAG_WIFI_DISCONNECTION -> {
val payload = event.data as Array<*> val data = payload as Array<*>
buildJsonObject { SecurityEventData.WifiDisconnection(data[0] as String, data[1] as String)
put("bssid", payload[0] as String)
(payload[1] as String).let { if(it != "") put("reason", it) }
}
} }
SecurityLog.TAG_WIPE_FAILURE -> null SecurityLog.TAG_WIPE_FAILURE -> null
else -> 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() { fun setDefaultAffiliationID() {
if (VERSION.SDK_INT < 26) return if (VERSION.SDK_INT < 26) return
if(!SP.isDefaultAffiliationIdSet) { 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 @Serializable object PreferentialNetworkService
@RequiresApi(33) @RequiresApi(33)

View File

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

View File

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

View File

@@ -35,7 +35,6 @@
<string name="time_unit_ms">Zaman (ms)</string> <string name="time_unit_ms">Zaman (ms)</string>
<string name="length">Uzunluk</string> <string name="length">Uzunluk</string>
<string name="none">Yok</string> <string name="none">Yok</string>
<string name="no_logs">Kayıt Yok</string>
<string name="default_stringres">Varsayılan</string> <string name="default_stringres">Varsayılan</string>
<string name="apply">Uygula</string> <string name="apply">Uygula</string>
<string name="decide_by_user">Kullanıcı Tarafından Karar Ver</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="time_unit_ms">时间(毫秒)</string>
<string name="length">长度</string> <string name="length">长度</string>
<string name="none"></string> <string name="none"></string>
<string name="no_logs">无日志</string>
<string name="default_stringres">默认</string> <string name="default_stringres">默认</string>
<string name="apply">应用</string> <string name="apply">应用</string>
<string name="decide_by_user">由用户决定</string> <string name="decide_by_user">由用户决定</string>
@@ -194,6 +193,8 @@
<string name="uninstall_all_user_ca_cert">卸载所有用户证书</string> <string name="uninstall_all_user_ca_cert">卸载所有用户证书</string>
<string name="security_logging">安全日志</string> <string name="security_logging">安全日志</string>
<string name="pre_reboot_security_logs">重启前安全日志</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_data">清除数据</string>
<string name="wipe_external_storage">清除外部存储</string> <string name="wipe_external_storage">清除外部存储</string>
<string name="wipe_reset_protection_data">清除受保护的数据</string> <string name="wipe_reset_protection_data">清除受保护的数据</string>

View File

@@ -37,7 +37,6 @@
<string name="time_unit_ms">Time(ms)</string> <string name="time_unit_ms">Time(ms)</string>
<string name="length">Length</string> <string name="length">Length</string>
<string name="none">None</string> <string name="none">None</string>
<string name="no_logs">No logs</string>
<string name="default_stringres">Default</string> <string name="default_stringres">Default</string>
<string name="apply">Apply</string> <string name="apply">Apply</string>
<string name="decide_by_user">Decide by user</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="uninstall_all_user_ca_cert">Uninstall all user CA certificate</string>
<string name="security_logging">Security logging</string> <string name="security_logging">Security logging</string>
<string name="pre_reboot_security_logs">Pre-reboot security logs</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_data">Wipe data</string>
<string name="wipe_external_storage">Wipe external storage</string> <string name="wipe_external_storage">Wipe external storage</string>
<string name="wipe_reset_protection_data">Wipe protected data</string> <string name="wipe_reset_protection_data">Wipe protected data</string>