mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
Retrieve security logs
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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,12 +79,13 @@ 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 ->
|
||||||
activityResult.data.let {
|
activityResult.data.let {
|
||||||
if(it==null){
|
if(it == null){
|
||||||
Toast.makeText(context.applicationContext, R.string.file_not_exist, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context.applicationContext, R.string.file_not_exist, Toast.LENGTH_SHORT).show()
|
||||||
}else{
|
}else{
|
||||||
fileUriFlow.value = it.data
|
fileUriFlow.value = it.data
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)))
|
||||||
Button(
|
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||||
onClick = {
|
Button(
|
||||||
val log = dpm.retrieveSecurityLogs(receiver)
|
onClick = {
|
||||||
if(log!=null) {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
for(i in log) { Log.d("SecureLog",i.toString()) }
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
intent.setType("application/json")
|
||||||
}else{
|
intent.putExtra(Intent.EXTRA_TITLE, "SecurityLogs.json")
|
||||||
Log.d("SecureLog", context.getString(R.string.none))
|
exportFilePath.value = logFile.path
|
||||||
Toast.makeText(context, R.string.no_logs, Toast.LENGTH_SHORT).show()
|
exportFile.launch(intent)
|
||||||
}
|
},
|
||||||
},
|
enabled = fileSize > 0,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth(0.49F)
|
||||||
) {
|
) {
|
||||||
Text(stringResource(R.string.security_logs))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(Modifier.padding(vertical = 5.dp))
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
val log = dpm.retrievePreRebootSecurityLogs(receiver)
|
val logs = dpm.retrievePreRebootSecurityLogs(receiver)
|
||||||
if(log!=null) {
|
if(logs == null) {
|
||||||
for(i in log) { Log.d("SecureLog",i.toString()) }
|
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
|
||||||
}else{
|
|
||||||
Log.d("SecureLog", context.getString(R.string.none))
|
|
||||||
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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user