Retrieve security logs

This commit is contained in:
BinTianqi
2024-09-01 09:45:39 +08:00
parent 03a42429b6
commit 4799a15909
8 changed files with 148 additions and 57 deletions

View File

@@ -23,6 +23,7 @@ import android.widget.Toast
import com.bintianqi.owndroid.dpm.getDPM import com.bintianqi.owndroid.dpm.getDPM
import com.bintianqi.owndroid.dpm.getReceiver import com.bintianqi.owndroid.dpm.getReceiver
import com.bintianqi.owndroid.dpm.handleNetworkLogs import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handleSecurityLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner import com.bintianqi.owndroid.dpm.isProfileOwner
@@ -54,12 +55,19 @@ class Receiver : DeviceAdminReceiver() {
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) { override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount) super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
if(VERSION.SDK_INT >= 28) { if(VERSION.SDK_INT >= 26) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
handleNetworkLogs(context, batchToken) handleNetworkLogs(context, batchToken)
} }
} }
} }
override fun onSecurityLogsAvailable(context: Context, intent: Intent) {
super.onSecurityLogsAvailable(context, intent)
if(VERSION.SDK_INT >= 24) {
handleSecurityLogs(context)
}
}
} }
val installAppDone = MutableStateFlow(false) val installAppDone = MutableStateFlow(false)

View File

@@ -2,7 +2,11 @@ package com.bintianqi.owndroid
import android.Manifest import android.Manifest
import android.app.admin.DevicePolicyManager import android.app.admin.DevicePolicyManager
import android.content.* import android.content.ClipData
import android.content.ClipboardManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
@@ -17,7 +21,6 @@ import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.nio.file.Files
import java.util.Locale import java.util.Locale
lateinit var getFile: ActivityResultLauncher<Intent> lateinit var getFile: ActivityResultLauncher<Intent>
@@ -76,7 +79,8 @@ fun writeClipBoard(context: Context, string: String):Boolean{
} }
lateinit var requestPermission: ActivityResultLauncher<String> lateinit var requestPermission: ActivityResultLauncher<String>
lateinit var saveNetworkLogs: ActivityResultLauncher<Intent> lateinit var exportFile: ActivityResultLauncher<Intent>
val exportFilePath = MutableStateFlow<String?>(null)
fun registerActivityResult(context: ComponentActivity){ fun registerActivityResult(context: ComponentActivity){
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult -> getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
@@ -96,13 +100,13 @@ fun registerActivityResult(context: ComponentActivity){
} }
} }
requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it } requestPermission = context.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted.value = it }
saveNetworkLogs = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> exportFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val intentData = result.data ?: return@registerForActivityResult val intentData = result.data ?: return@registerForActivityResult
val uriData = intentData.data ?: return@registerForActivityResult val uriData = intentData.data ?: return@registerForActivityResult
val path = exportFilePath.value ?: return@registerForActivityResult
context.contentResolver.openOutputStream(uriData).use { outStream -> context.contentResolver.openOutputStream(uriData).use { outStream ->
if(outStream != null) { if(outStream != null) {
val logFile = context.filesDir.resolve("NetworkLogs.json") File(path).inputStream().use { inStream ->
logFile.inputStream().use { inStream ->
inStream.copyTo(outStream) inStream.copyTo(outStream)
} }
Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show() Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show()

View File

@@ -15,7 +15,6 @@ import android.content.Intent
import android.content.pm.IPackageInstaller import android.content.pm.IPackageInstaller
import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build
import android.os.Build.VERSION import android.os.Build.VERSION
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
@@ -320,41 +319,39 @@ fun permissionList(): List<PermissionItem>{
return list return list
} }
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(26)
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
fun handleNetworkLogs(context: Context, batchToken: Long) { fun handleNetworkLogs(context: Context, batchToken: Long) {
val events = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return val networkEvents = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val eventsList = mutableListOf<NetworkEventItem>()
val file = context.filesDir.toPath().resolve("NetworkLogs.json") val file = context.filesDir.toPath().resolve("NetworkLogs.json")
if(file.notExists()) file.writeText("[]") if(file.notExists()) file.writeText("[]")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false } val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
var jsonObj: MutableList<NetworkEventItem> var events: MutableList<NetworkEventItem>
file.inputStream().use { file.inputStream().use {
jsonObj = json.decodeFromStream(it) events = json.decodeFromStream(it)
} }
events.forEach { event -> networkEvents.forEach { event ->
try { try {
val dnsEvent = event as DnsEvent val dnsEvent = event as DnsEvent
val addresses = mutableListOf<String?>() val addresses = mutableListOf<String?>()
dnsEvent.inetAddresses.forEach { inetAddresses -> dnsEvent.inetAddresses.forEach { inetAddresses ->
addresses += inetAddresses.hostAddress addresses += inetAddresses.hostAddress
} }
eventsList += NetworkEventItem( events += NetworkEventItem(
id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName
, timestamp = event.timestamp, type = "dns", hostName = dnsEvent.hostname, , timestamp = event.timestamp, type = "dns", hostName = dnsEvent.hostname,
hostAddresses = addresses, totalResolvedAddressCount = dnsEvent.totalResolvedAddressCount hostAddresses = addresses, totalResolvedAddressCount = dnsEvent.totalResolvedAddressCount
) )
} catch(e: Exception) { } catch(e: Exception) {
val connectEvent = event as ConnectEvent val connectEvent = event as ConnectEvent
eventsList += NetworkEventItem( events += NetworkEventItem(
id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName, timestamp = event.timestamp, type = "connect", id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName, timestamp = event.timestamp, type = "connect",
hostAddress = connectEvent.inetAddress.hostAddress, port = connectEvent.port hostAddress = connectEvent.inetAddress.hostAddress, port = connectEvent.port
) )
} }
} }
jsonObj.addAll(eventsList)
file.outputStream().use { file.outputStream().use {
json.encodeToStream(jsonObj, it) json.encodeToStream(events, it)
} }
} }
@@ -364,9 +361,43 @@ data class NetworkEventItem(
@SerialName("package_name") val packageName: String, @SerialName("package_name") val packageName: String,
val timestamp: Long, val timestamp: Long,
val type: String, val type: String,
val port: Int? = null,
@SerialName("address") val hostAddress: String? = null, @SerialName("address") val hostAddress: String? = null,
val port: Int? = null,
@SerialName("host_name") val hostName: String? = null, @SerialName("host_name") val hostName: String? = null,
@SerialName("count") val totalResolvedAddressCount: Int? = null, @SerialName("count") val totalResolvedAddressCount: Int? = null,
@SerialName("addresses") val hostAddresses: List<String?>? = null @SerialName("addresses") val hostAddresses: List<String?>? = null
) )
@OptIn(ExperimentalSerializationApi::class)
@RequiresApi(24)
fun handleSecurityLogs(context: Context) {
val file = context.filesDir.resolve("SecurityLogs.json")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
if(!file.exists()) file.writeText("[]")
val securityEvents = context.getDPM().retrieveSecurityLogs(context.getReceiver())
securityEvents ?: return
val logs: MutableList<SecurityEventItem>
file.inputStream().use {
logs = json.decodeFromStream(it)
}
securityEvents.forEach {
logs += SecurityEventItem(
id = if(VERSION.SDK_INT >= 28) it.id else null,
tag = it.tag, timeNanos = it.timeNanos,
logLevel = if(VERSION.SDK_INT >= 28) it.logLevel else null,
data = it.data.toString()
)
}
file.outputStream().use {
json.encodeToStream(logs, it)
}
}
@Serializable
data class SecurityEventItem(
val id: Long?,
val tag: Int,
@SerialName("time_nanos") val timeNanos: Long,
@SerialName("log_level") val logLevel: Int?,
val data: String
)

View File

@@ -99,8 +99,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
import com.bintianqi.owndroid.formatFileSize import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.saveNetworkLogs
import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.selectedPackage
import com.bintianqi.owndroid.toText import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.Animations import com.bintianqi.owndroid.ui.Animations
@@ -169,6 +170,8 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
val receiver = context.getReceiver() val receiver = context.getReceiver()
val deviceOwner = context.isDeviceOwner val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner val profileOwner = context.isProfileOwner
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dhizuku = sharedPref.getBoolean("dhizuku", false)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) { Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
Text( Text(
text = stringResource(R.string.network), text = stringResource(R.string.network),
@@ -196,7 +199,7 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
if(deviceOwner) { if(deviceOwner) {
SubPageItem(R.string.recommended_global_proxy, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") } SubPageItem(R.string.recommended_global_proxy, "", R.drawable.vpn_key_fill0) { navCtrl.navigate("RecommendedGlobalProxy") }
} }
if(VERSION.SDK_INT >= 26&&(deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) { if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || (profileOwner && dpm.isManagedProfile(receiver)))) {
SubPageItem(R.string.retrieve_net_logs, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") } SubPageItem(R.string.retrieve_net_logs, "", R.drawable.description_fill0) { navCtrl.navigate("NetworkLog") }
} }
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) { if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
@@ -629,7 +632,6 @@ private fun NetworkLog() {
val receiver = context.getReceiver() val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("NetworkLogs.json") val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) } var fileSize by remember { mutableLongStateOf(0) }
var fileExists by remember { mutableStateOf(logFile.exists()) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
fileSize = logFile.length() fileSize = logFile.length()
} }
@@ -638,26 +640,29 @@ private fun NetworkLog() {
Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge) Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false) SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
if(fileExists) { Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Text(stringResource(R.string.retrieved_logs_are, formatFileSize(fileSize))) Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button( Button(
onClick = { onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json") intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json") intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
saveNetworkLogs.launch(intent) exportFilePath.value = logFile.path
exportFile.launch(intent)
}, },
modifier = Modifier.fillMaxWidth() enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
) { ) {
Text(stringResource(R.string.export_logs)) Text(stringResource(R.string.export_logs))
} }
Button( Button(
onClick = { onClick = {
Toast.makeText(context, if(logFile.delete()) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show() logFile.delete()
fileExists = logFile.exists() fileSize = logFile.length()
}, },
modifier = Modifier.fillMaxWidth() enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.96F)
) { ) {
Text(stringResource(R.string.delete_logs)) Text(stringResource(R.string.delete_logs))
} }

View File

@@ -41,7 +41,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import android.os.UserManager import android.os.UserManager
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
@@ -78,6 +77,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
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
@@ -103,7 +103,10 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.StopLockTaskModeReceiver import com.bintianqi.owndroid.StopLockTaskModeReceiver
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
import com.bintianqi.owndroid.fileUriFlow import com.bintianqi.owndroid.fileUriFlow
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.getFile import com.bintianqi.owndroid.getFile
import com.bintianqi.owndroid.prepareForNotification import com.bintianqi.owndroid.prepareForNotification
import com.bintianqi.owndroid.selectedPackage import com.bintianqi.owndroid.selectedPackage
@@ -118,6 +121,9 @@ import com.bintianqi.owndroid.ui.SwitchItem
import com.bintianqi.owndroid.ui.TopBar import com.bintianqi.owndroid.ui.TopBar
import com.bintianqi.owndroid.uriToStream import com.bintianqi.owndroid.uriToStream
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import java.util.Date import java.util.Date
import java.util.TimeZone import java.util.TimeZone
import java.util.concurrent.Executors import java.util.concurrent.Executors
@@ -181,6 +187,7 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia
val dpm = context.getDPM() val dpm = context.getDPM()
val receiver = context.getReceiver() val receiver = context.getReceiver()
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE) val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dhizuku = sharedPref.getBoolean("dhizuku", false)
val dangerousFeatures = sharedPref.getBoolean("dangerous_features", false) val dangerousFeatures = sharedPref.getBoolean("dangerous_features", false)
val deviceOwner = context.isDeviceOwner val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner val profileOwner = context.isProfileOwner
@@ -219,7 +226,7 @@ private fun Home(navCtrl: NavHostController, scrollState: ScrollState, rebootDia
if(deviceOwner || profileOwner) { if(deviceOwner || profileOwner) {
SubPageItem(R.string.ca_cert, "", R.drawable.license_fill0) { navCtrl.navigate("CaCert") } SubPageItem(R.string.ca_cert, "", R.drawable.license_fill0) { navCtrl.navigate("CaCert") }
} }
if(VERSION.SDK_INT >= 26 && (deviceOwner || dpm.isOrgProfile(receiver))) { if(VERSION.SDK_INT >= 26 && !dhizuku && (deviceOwner || dpm.isOrgProfile(receiver))) {
SubPageItem(R.string.security_logs, "", R.drawable.description_fill0) { navCtrl.navigate("SecurityLogs") } SubPageItem(R.string.security_logs, "", R.drawable.description_fill0) { navCtrl.navigate("SecurityLogs") }
} }
if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) { if(VERSION.SDK_INT >= 23 && (deviceOwner || dpm.isOrgProfile(receiver))) {
@@ -981,42 +988,78 @@ private fun CaCert() {
} }
} }
@OptIn(ExperimentalSerializationApi::class)
@SuppressLint("NewApi") @SuppressLint("NewApi")
@Composable @Composable
private fun SecurityLogs() { private fun SecurityLogs() {
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM() val dpm = context.getDPM()
val receiver = context.getReceiver() val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("SecurityLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
LaunchedEffect(Unit) {
fileSize = logFile.length()
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) { Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp)) Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.security_logs), style = typography.headlineLarge) Text(text = stringResource(R.string.security_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp)) Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.developing))
SwitchItem(R.string.enable, "", null, { dpm.isSecurityLoggingEnabled(receiver) }, { dpm.setSecurityLoggingEnabled(receiver, it) }, padding = false) SwitchItem(R.string.enable, "", null, { dpm.isSecurityLoggingEnabled(receiver) }, { dpm.setSecurityLoggingEnabled(receiver, it) }, padding = false)
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
Button( Button(
onClick = { onClick = {
val log = dpm.retrieveSecurityLogs(receiver) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
if(log!=null) { intent.addCategory(Intent.CATEGORY_OPENABLE)
for(i in log) { Log.d("SecureLog",i.toString()) } intent.setType("application/json")
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json")
}else{ exportFilePath.value = logFile.path
Log.d("SecureLog", context.getString(R.string.none)) exportFile.launch(intent)
Toast.makeText(context, R.string.no_logs, Toast.LENGTH_SHORT).show()
}
}, },
modifier = Modifier.fillMaxWidth() enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
) { ) {
Text(stringResource(R.string.security_logs)) Text(stringResource(R.string.export_logs))
} }
Button( Button(
onClick = { onClick = {
val log = dpm.retrievePreRebootSecurityLogs(receiver) logFile.delete()
if(log!=null) { fileSize = logFile.length()
for(i in log) { Log.d("SecureLog",i.toString()) } },
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show() enabled = fileSize > 0,
}else{ modifier = Modifier.fillMaxWidth(0.96F)
Log.d("SecureLog", context.getString(R.string.none)) ) {
Text(stringResource(R.string.delete_logs))
}
}
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val logs = dpm.retrievePreRebootSecurityLogs(receiver)
if(logs == null) {
Toast.makeText(context, R.string.no_logs, Toast.LENGTH_SHORT).show() Toast.makeText(context, R.string.no_logs, Toast.LENGTH_SHORT).show()
return@Button
} else {
val logsList = mutableListOf<SecurityEventItem>()
logs.forEach {
logsList += SecurityEventItem(
id = if(VERSION.SDK_INT >= 28) it.id else null,
tag = it.tag, timeNanos = it.timeNanos,
logLevel = if(VERSION.SDK_INT >= 28) it.logLevel else null,
data = it.data.toString()
)
}
val preRebootSecurityLogs = context.filesDir.resolve("PreRebootSecurityLogs")
preRebootSecurityLogs.outputStream().use {
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
json.encodeToStream(logsList, it)
}
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "PreRebootSecurityLogs.json")
exportFilePath.value = preRebootSecurityLogs.path
exportFile.launch(intent)
} }
}, },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()

View File

@@ -241,7 +241,7 @@
<string name="invalid_config">Invalid config</string> <!--TODO--> <string name="invalid_config">Invalid config</string> <!--TODO-->
<string name="exclude_hosts">Exclude hosts</string> <!--TODO--> <string name="exclude_hosts">Exclude hosts</string> <!--TODO-->
<string name="retrieve_net_logs">Ağ kayıtları</string> <string name="retrieve_net_logs">Ağ kayıtları</string>
<string name="retrieved_logs_are">Retrieved logs: %1$s</string> <!--TODO--> <string name="log_file_size_is">Log file size: %1$s</string> <!--TODO-->
<string name="delete_logs">Delete logs</string> <!--TODO--> <string name="delete_logs">Delete logs</string> <!--TODO-->
<string name="export_logs">Export logs</string> <!--TODO--> <string name="export_logs">Export logs</string> <!--TODO-->
<string name="retrieve">Geri al</string> <string name="retrieve">Geri al</string>

View File

@@ -236,7 +236,7 @@
<string name="invalid_config">无效配置</string> <string name="invalid_config">无效配置</string>
<string name="exclude_hosts">排除列表</string> <string name="exclude_hosts">排除列表</string>
<string name="retrieve_net_logs">收集网络日志</string> <string name="retrieve_net_logs">收集网络日志</string>
<string name="retrieved_logs_are">已收集的日志%1$s</string> <string name="log_file_size_is">日志文件大小%1$s</string>
<string name="delete_logs">删除日志</string> <string name="delete_logs">删除日志</string>
<string name="export_logs">导出日志</string> <string name="export_logs">导出日志</string>
<string name="retrieve">收集</string> <string name="retrieve">收集</string>

View File

@@ -245,7 +245,7 @@
<string name="invalid_config">Invalid config</string> <string name="invalid_config">Invalid config</string>
<string name="exclude_hosts">Exclude hosts</string> <string name="exclude_hosts">Exclude hosts</string>
<string name="retrieve_net_logs">Network logs</string> <string name="retrieve_net_logs">Network logs</string>
<string name="retrieved_logs_are">Retrieved logs: %1$s</string> <string name="log_file_size_is">Log file size: %1$s</string>
<string name="delete_logs">Delete logs</string> <string name="delete_logs">Delete logs</string>
<string name="export_logs">Export logs</string> <string name="export_logs">Export logs</string>
<string name="retrieve">Retrieve</string> <string name="retrieve">Retrieve</string>