Retrieve network logs

This commit is contained in:
BinTianqi
2024-08-31 12:00:08 +08:00
parent 99e02df084
commit 03a42429b6
9 changed files with 155 additions and 20 deletions

View File

@@ -3,7 +3,9 @@ package com.bintianqi.owndroid.dpm
import android.Manifest
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.app.admin.ConnectEvent
import android.app.admin.DevicePolicyManager
import android.app.admin.DnsEvent
import android.app.admin.FactoryResetProtectionPolicy
import android.app.admin.IDevicePolicyManager
import android.app.admin.SystemUpdatePolicy
@@ -13,9 +15,11 @@ import android.content.Intent
import android.content.pm.IPackageInstaller
import android.content.pm.PackageInstaller
import android.content.pm.PackageManager
import android.os.Build
import android.os.Build.VERSION
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import com.bintianqi.owndroid.InstallAppActivity
import com.bintianqi.owndroid.PackageInstallerReceiver
@@ -26,8 +30,18 @@ import com.rosan.dhizuku.api.Dhizuku
import com.rosan.dhizuku.api.Dhizuku.binderWrapper
import com.rosan.dhizuku.api.DhizukuBinderWrapper
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import java.io.IOException
import java.io.InputStream
import kotlin.io.path.inputStream
import kotlin.io.path.notExists
import kotlin.io.path.outputStream
import kotlin.io.path.writeText
lateinit var createManagedProfile: ActivityResultLauncher<Intent>
lateinit var addDeviceAdmin: ActivityResultLauncher<Intent>
@@ -305,3 +319,54 @@ fun permissionList(): List<PermissionItem>{
}
return list
}
@RequiresApi(Build.VERSION_CODES.O)
@OptIn(ExperimentalSerializationApi::class)
fun handleNetworkLogs(context: Context, batchToken: Long) {
val events = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val eventsList = mutableListOf<NetworkEventItem>()
val file = context.filesDir.toPath().resolve("NetworkLogs.json")
if(file.notExists()) file.writeText("[]")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
var jsonObj: MutableList<NetworkEventItem>
file.inputStream().use {
jsonObj = json.decodeFromStream(it)
}
events.forEach { event ->
try {
val dnsEvent = event as DnsEvent
val addresses = mutableListOf<String?>()
dnsEvent.inetAddresses.forEach { inetAddresses ->
addresses += inetAddresses.hostAddress
}
eventsList += NetworkEventItem(
id = if(VERSION.SDK_INT >= 28) event.id else null, packageName = event.packageName
, timestamp = event.timestamp, type = "dns", hostName = dnsEvent.hostname,
hostAddresses = addresses, totalResolvedAddressCount = dnsEvent.totalResolvedAddressCount
)
} catch(e: Exception) {
val connectEvent = event as ConnectEvent
eventsList += NetworkEventItem(
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
)
}
}
jsonObj.addAll(eventsList)
file.outputStream().use {
json.encodeToStream(jsonObj, it)
}
}
@Serializable
data class NetworkEventItem(
val id: Long? = null,
@SerialName("package_name") val packageName: String,
val timestamp: Long,
val type: String,
val port: Int? = null,
@SerialName("address") val hostAddress: String? = null,
@SerialName("host_name") val hostName: String? = null,
@SerialName("count") val totalResolvedAddressCount: Int? = null,
@SerialName("addresses") val hostAddresses: List<String?>? = null
)

View File

@@ -16,6 +16,7 @@ import android.app.admin.WifiSsidPolicy
import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_ALLOWLIST
import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager.NameNotFoundException
import android.net.ProxyInfo
import android.net.Uri
@@ -38,7 +39,6 @@ import android.telephony.data.ApnSetting.PROTOCOL_IPV6
import android.telephony.data.ApnSetting.PROTOCOL_NON_IP
import android.telephony.data.ApnSetting.PROTOCOL_PPP
import android.telephony.data.ApnSetting.PROTOCOL_UNSTRUCTURED
import android.util.Log
import android.widget.Toast
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
@@ -75,6 +75,7 @@ import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -98,6 +99,8 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.formatFileSize
import com.bintianqi.owndroid.saveNetworkLogs
import com.bintianqi.owndroid.selectedPackage
import com.bintianqi.owndroid.toText
import com.bintianqi.owndroid.ui.Animations
@@ -624,28 +627,40 @@ private fun NetworkLog() {
val context = LocalContext.current
val dpm = context.getDPM()
val receiver = context.getReceiver()
val logFile = context.filesDir.resolve("NetworkLogs.json")
var fileSize by remember { mutableLongStateOf(0) }
var fileExists by remember { mutableStateOf(logFile.exists()) }
LaunchedEffect(Unit) {
fileSize = logFile.length()
}
Column(modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp).verticalScroll(rememberScrollState())) {
Spacer(Modifier.padding(vertical = 10.dp))
Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
Text(text = stringResource(R.string.developing))
Spacer(Modifier.padding(vertical = 5.dp))
SwitchItem(R.string.enable,"",null, { dpm.isNetworkLoggingEnabled(receiver) }, {dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
Spacer(Modifier.padding(vertical = 5.dp))
Button(
onClick = {
val log = dpm.retrieveNetworkLogs(receiver,1234567890)
if(log != null) {
for(i in log) { Log.d("NetworkLog",i.toString()) }
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
}else{
Log.d("NetworkLog",context.getString(R.string.none))
Toast.makeText(context, R.string.none, Toast.LENGTH_SHORT).show()
}
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.retrieve))
SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
if(fileExists) {
Text(stringResource(R.string.retrieved_logs_are, formatFileSize(fileSize)))
Button(
onClick = {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.setType("application/json")
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
saveNetworkLogs.launch(intent)
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.export_logs))
}
Button(
onClick = {
Toast.makeText(context, if(logFile.delete()) R.string.success else R.string.failed, Toast.LENGTH_SHORT).show()
fileExists = logFile.exists()
},
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.delete_logs))
}
}
}
}