Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v6.1 #80

Merged
merged 8 commits into from
Oct 26, 2024
Merged

v6.1 #80

Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Retrieve security logs
  • Loading branch information
BinTianqi committed Sep 1, 2024
commit 4799a15909d0a4c4fb4e8c2f7febaf5aa825eb50
10 changes: 9 additions & 1 deletion app/src/main/java/com/bintianqi/owndroid/Receiver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.widget.Toast
import com.bintianqi.owndroid.dpm.getDPM
import com.bintianqi.owndroid.dpm.getReceiver
import com.bintianqi.owndroid.dpm.handleNetworkLogs
import com.bintianqi.owndroid.dpm.handleSecurityLogs
import com.bintianqi.owndroid.dpm.isDeviceAdmin
import com.bintianqi.owndroid.dpm.isDeviceOwner
import com.bintianqi.owndroid.dpm.isProfileOwner
Expand Down Expand Up @@ -54,12 +55,19 @@ class Receiver : DeviceAdminReceiver() {

override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
if(VERSION.SDK_INT >= 28) {
if(VERSION.SDK_INT >= 26) {
CoroutineScope(Dispatchers.IO).launch {
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)
Expand Down
18 changes: 11 additions & 7 deletions app/src/main/java/com/bintianqi/owndroid/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package com.bintianqi.owndroid

import android.Manifest
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.net.Uri
import android.os.Build.VERSION
Expand All @@ -17,7 +21,6 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.nio.file.Files
import java.util.Locale

lateinit var getFile: ActivityResultLauncher<Intent>
Expand Down Expand Up @@ -76,12 +79,13 @@ fun writeClipBoard(context: Context, string: String):Boolean{
}

lateinit var requestPermission: ActivityResultLauncher<String>
lateinit var saveNetworkLogs: ActivityResultLauncher<Intent>
lateinit var exportFile: ActivityResultLauncher<Intent>
val exportFilePath = MutableStateFlow<String?>(null)

fun registerActivityResult(context: ComponentActivity){
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
activityResult.data.let {
if(it==null){
if(it == null){
Toast.makeText(context.applicationContext, R.string.file_not_exist, Toast.LENGTH_SHORT).show()
}else{
fileUriFlow.value = it.data
Expand All @@ -96,13 +100,13 @@ fun registerActivityResult(context: ComponentActivity){
}
}
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 uriData = intentData.data ?: return@registerForActivityResult
val path = exportFilePath.value ?: return@registerForActivityResult
context.contentResolver.openOutputStream(uriData).use { outStream ->
if(outStream != null) {
val logFile = context.filesDir.resolve("NetworkLogs.json")
logFile.inputStream().use { inStream ->
File(path).inputStream().use { inStream ->
inStream.copyTo(outStream)
}
Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show()
Expand Down
55 changes: 43 additions & 12 deletions app/src/main/java/com/bintianqi/owndroid/dpm/DPM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ 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
Expand Down Expand Up @@ -320,41 +319,39 @@ fun permissionList(): List<PermissionItem>{
return list
}

@RequiresApi(Build.VERSION_CODES.O)
@RequiresApi(26)
@OptIn(ExperimentalSerializationApi::class)
fun handleNetworkLogs(context: Context, batchToken: Long) {
val events = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val eventsList = mutableListOf<NetworkEventItem>()
val networkEvents = context.getDPM().retrieveNetworkLogs(context.getReceiver(), batchToken) ?: return
val file = context.filesDir.toPath().resolve("NetworkLogs.json")
if(file.notExists()) file.writeText("[]")
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
var jsonObj: MutableList<NetworkEventItem>
var events: MutableList<NetworkEventItem>
file.inputStream().use {
jsonObj = json.decodeFromStream(it)
events = json.decodeFromStream(it)
}
events.forEach { event ->
networkEvents.forEach { event ->
try {
val dnsEvent = event as DnsEvent
val addresses = mutableListOf<String?>()
dnsEvent.inetAddresses.forEach { inetAddresses ->
addresses += inetAddresses.hostAddress
}
eventsList += NetworkEventItem(
events += 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(
events += 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)
json.encodeToStream(events, it)
}
}

Expand All @@ -364,9 +361,43 @@ data class NetworkEventItem(
@SerialName("package_name") val packageName: String,
val timestamp: Long,
val type: String,
val port: Int? = null,
@SerialName("address") val hostAddress: String? = null,
val port: Int? = null,
@SerialName("host_name") val hostName: String? = null,
@SerialName("count") val totalResolvedAddressCount: Int? = 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
)
25 changes: 15 additions & 10 deletions app/src/main/java/com/bintianqi/owndroid/dpm/Network.kt
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.bintianqi.owndroid.R
import com.bintianqi.owndroid.exportFile
import com.bintianqi.owndroid.exportFilePath
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
Expand Down Expand Up @@ -169,6 +170,8 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
val receiver = context.getReceiver()
val deviceOwner = context.isDeviceOwner
val profileOwner = context.isProfileOwner
val sharedPref = context.getSharedPreferences("data", Context.MODE_PRIVATE)
val dhizuku = sharedPref.getBoolean("dhizuku", false)
Column(modifier = Modifier.fillMaxSize().verticalScroll(scrollState)) {
Text(
text = stringResource(R.string.network),
Expand Down Expand Up @@ -196,7 +199,7 @@ private fun Home(navCtrl:NavHostController, scrollState: ScrollState, wifiMacDia
if(deviceOwner) {
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") }
}
if(VERSION.SDK_INT >= 31 && (deviceOwner || profileOwner)) {
Expand Down Expand Up @@ -629,7 +632,6 @@ private fun NetworkLog() {
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()
}
Expand All @@ -638,26 +640,29 @@ private fun NetworkLog() {
Text(text = stringResource(R.string.retrieve_net_logs), style = typography.headlineLarge)
Spacer(Modifier.padding(vertical = 5.dp))
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)))
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
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)
exportFilePath.value = logFile.path
exportFile.launch(intent)
},
modifier = Modifier.fillMaxWidth()
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.49F)
) {
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()
logFile.delete()
fileSize = logFile.length()
},
modifier = Modifier.fillMaxWidth()
enabled = fileSize > 0,
modifier = Modifier.fillMaxWidth(0.96F)
) {
Text(stringResource(R.string.delete_logs))
}
Expand Down
Loading