mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
Retrieve network logs
This commit is contained in:
@@ -2,6 +2,7 @@ plugins {
|
|||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.cc)
|
alias(libs.plugins.cc)
|
||||||
|
kotlin("plugin.serialization") version "2.0.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -86,4 +87,5 @@ dependencies {
|
|||||||
implementation(libs.androidx.biometric)
|
implementation(libs.androidx.biometric)
|
||||||
implementation(libs.androidx.fragment)
|
implementation(libs.androidx.fragment)
|
||||||
implementation(libs.hiddenApiBypass)
|
implementation(libs.hiddenApiBypass)
|
||||||
|
implementation(libs.serialization)
|
||||||
}
|
}
|
||||||
@@ -17,16 +17,20 @@ import android.content.pm.PackageInstaller.STATUS_FAILURE_STORAGE
|
|||||||
import android.content.pm.PackageInstaller.STATUS_FAILURE_TIMEOUT
|
import android.content.pm.PackageInstaller.STATUS_FAILURE_TIMEOUT
|
||||||
import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
|
import android.content.pm.PackageInstaller.STATUS_PENDING_USER_ACTION
|
||||||
import android.content.pm.PackageInstaller.STATUS_SUCCESS
|
import android.content.pm.PackageInstaller.STATUS_SUCCESS
|
||||||
|
import android.os.Build.VERSION
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
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.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
|
||||||
import com.bintianqi.owndroid.dpm.toggleInstallAppActivity
|
import com.bintianqi.owndroid.dpm.toggleInstallAppActivity
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class Receiver : DeviceAdminReceiver() {
|
class Receiver : DeviceAdminReceiver() {
|
||||||
override fun onEnabled(context: Context, intent: Intent) {
|
override fun onEnabled(context: Context, intent: Intent) {
|
||||||
@@ -48,6 +52,14 @@ class Receiver : DeviceAdminReceiver() {
|
|||||||
Toast.makeText(context, R.string.create_work_profile_success, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.create_work_profile_success, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
|
||||||
|
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
|
||||||
|
if(VERSION.SDK_INT >= 28) {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
handleNetworkLogs(context, batchToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val installAppDone = MutableStateFlow(false)
|
val installAppDone = MutableStateFlow(false)
|
||||||
|
|||||||
@@ -13,9 +13,12 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import com.bintianqi.owndroid.dpm.addDeviceAdmin
|
import com.bintianqi.owndroid.dpm.addDeviceAdmin
|
||||||
import com.bintianqi.owndroid.dpm.createManagedProfile
|
import com.bintianqi.owndroid.dpm.createManagedProfile
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
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
|
||||||
|
|
||||||
lateinit var getFile: ActivityResultLauncher<Intent>
|
lateinit var getFile: ActivityResultLauncher<Intent>
|
||||||
val fileUriFlow = MutableStateFlow(Uri.parse(""))
|
val fileUriFlow = MutableStateFlow(Uri.parse(""))
|
||||||
@@ -73,6 +76,7 @@ fun writeClipBoard(context: Context, string: String):Boolean{
|
|||||||
}
|
}
|
||||||
|
|
||||||
lateinit var requestPermission: ActivityResultLauncher<String>
|
lateinit var requestPermission: ActivityResultLauncher<String>
|
||||||
|
lateinit var saveNetworkLogs: ActivityResultLauncher<Intent>
|
||||||
|
|
||||||
fun registerActivityResult(context: ComponentActivity){
|
fun registerActivityResult(context: ComponentActivity){
|
||||||
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
|
getFile = context.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { activityResult ->
|
||||||
@@ -92,6 +96,19 @@ 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 ->
|
||||||
|
val intentData = result.data ?: return@registerForActivityResult
|
||||||
|
val uriData = intentData.data ?: return@registerForActivityResult
|
||||||
|
context.contentResolver.openOutputStream(uriData).use { outStream ->
|
||||||
|
if(outStream != null) {
|
||||||
|
val logFile = context.filesDir.resolve("NetworkLogs.json")
|
||||||
|
logFile.inputStream().use { inStream ->
|
||||||
|
inStream.copyTo(outStream)
|
||||||
|
}
|
||||||
|
Toast.makeText(context.applicationContext, R.string.success, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val permissionGranted = MutableStateFlow<Boolean?>(null)
|
val permissionGranted = MutableStateFlow<Boolean?>(null)
|
||||||
@@ -108,3 +125,15 @@ suspend fun prepareForNotification(context: Context, action: ()->Unit) {
|
|||||||
action()
|
action()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun formatFileSize(bytes: Long): String {
|
||||||
|
val kb = 1024
|
||||||
|
val mb = kb * 1024
|
||||||
|
val gb = mb * 1024
|
||||||
|
return when {
|
||||||
|
bytes >= gb -> String.format(Locale.US, "%.2f GB", bytes / gb.toDouble())
|
||||||
|
bytes >= mb -> String.format(Locale.US, "%.2f MB", bytes / mb.toDouble())
|
||||||
|
bytes >= kb -> String.format(Locale.US, "%.2f KB", bytes / kb.toDouble())
|
||||||
|
else -> "$bytes bytes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package com.bintianqi.owndroid.dpm
|
|||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.admin.ConnectEvent
|
||||||
import android.app.admin.DevicePolicyManager
|
import android.app.admin.DevicePolicyManager
|
||||||
|
import android.app.admin.DnsEvent
|
||||||
import android.app.admin.FactoryResetProtectionPolicy
|
import android.app.admin.FactoryResetProtectionPolicy
|
||||||
import android.app.admin.IDevicePolicyManager
|
import android.app.admin.IDevicePolicyManager
|
||||||
import android.app.admin.SystemUpdatePolicy
|
import android.app.admin.SystemUpdatePolicy
|
||||||
@@ -13,9 +15,11 @@ 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
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import com.bintianqi.owndroid.InstallAppActivity
|
import com.bintianqi.owndroid.InstallAppActivity
|
||||||
import com.bintianqi.owndroid.PackageInstallerReceiver
|
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.Dhizuku.binderWrapper
|
||||||
import com.rosan.dhizuku.api.DhizukuBinderWrapper
|
import com.rosan.dhizuku.api.DhizukuBinderWrapper
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
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.IOException
|
||||||
import java.io.InputStream
|
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 createManagedProfile: ActivityResultLauncher<Intent>
|
||||||
lateinit var addDeviceAdmin: ActivityResultLauncher<Intent>
|
lateinit var addDeviceAdmin: ActivityResultLauncher<Intent>
|
||||||
@@ -305,3 +319,54 @@ fun permissionList(): List<PermissionItem>{
|
|||||||
}
|
}
|
||||||
return list
|
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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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_ALLOWLIST
|
||||||
import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST
|
import android.app.admin.WifiSsidPolicy.WIFI_SSID_POLICY_TYPE_DENYLIST
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.NameNotFoundException
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
import android.net.ProxyInfo
|
import android.net.ProxyInfo
|
||||||
import android.net.Uri
|
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_NON_IP
|
||||||
import android.telephony.data.ApnSetting.PROTOCOL_PPP
|
import android.telephony.data.ApnSetting.PROTOCOL_PPP
|
||||||
import android.telephony.data.ApnSetting.PROTOCOL_UNSTRUCTURED
|
import android.telephony.data.ApnSetting.PROTOCOL_UNSTRUCTURED
|
||||||
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
|
||||||
@@ -75,6 +75,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
|
||||||
@@ -98,6 +99,8 @@ 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.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
|
||||||
@@ -624,28 +627,40 @@ private fun NetworkLog() {
|
|||||||
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("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())) {
|
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.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))
|
||||||
Text(text = stringResource(R.string.developing))
|
SwitchItem(R.string.enable, "", null, { dpm.isNetworkLoggingEnabled(receiver) }, { dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
if(fileExists) {
|
||||||
SwitchItem(R.string.enable,"",null, { dpm.isNetworkLoggingEnabled(receiver) }, {dpm.setNetworkLoggingEnabled(receiver,it) }, padding = false)
|
Text(stringResource(R.string.retrieved_logs_are, formatFileSize(fileSize)))
|
||||||
Spacer(Modifier.padding(vertical = 5.dp))
|
Button(
|
||||||
Button(
|
onClick = {
|
||||||
onClick = {
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
||||||
val log = dpm.retrieveNetworkLogs(receiver,1234567890)
|
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
if(log != null) {
|
intent.setType("application/json")
|
||||||
for(i in log) { Log.d("NetworkLog",i.toString()) }
|
intent.putExtra(Intent.EXTRA_TITLE, "NetworkLogs.json")
|
||||||
Toast.makeText(context, R.string.success, Toast.LENGTH_SHORT).show()
|
saveNetworkLogs.launch(intent)
|
||||||
}else{
|
},
|
||||||
Log.d("NetworkLog",context.getString(R.string.none))
|
modifier = Modifier.fillMaxWidth()
|
||||||
Toast.makeText(context, R.string.none, Toast.LENGTH_SHORT).show()
|
) {
|
||||||
}
|
Text(stringResource(R.string.export_logs))
|
||||||
},
|
}
|
||||||
modifier = Modifier.fillMaxWidth()
|
Button(
|
||||||
) {
|
onClick = {
|
||||||
Text(stringResource(R.string.retrieve))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -241,6 +241,9 @@
|
|||||||
<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="delete_logs">Delete logs</string> <!--TODO-->
|
||||||
|
<string name="export_logs">Export logs</string> <!--TODO-->
|
||||||
<string name="retrieve">Geri al</string>
|
<string name="retrieve">Geri al</string>
|
||||||
<string name="wifi_auth_keypair">WiFi anahtar çifti</string>
|
<string name="wifi_auth_keypair">WiFi anahtar çifti</string>
|
||||||
<string name="keypair">Anahtar çifti</string>
|
<string name="keypair">Anahtar çifti</string>
|
||||||
|
|||||||
@@ -236,6 +236,9 @@
|
|||||||
<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="delete_logs">删除日志</string>
|
||||||
|
<string name="export_logs">导出日志</string>
|
||||||
<string name="retrieve">收集</string>
|
<string name="retrieve">收集</string>
|
||||||
<string name="wifi_auth_keypair">WiFi密钥对</string>
|
<string name="wifi_auth_keypair">WiFi密钥对</string>
|
||||||
<string name="keypair">密钥对</string>
|
<string name="keypair">密钥对</string>
|
||||||
|
|||||||
@@ -245,6 +245,9 @@
|
|||||||
<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="delete_logs">Delete logs</string>
|
||||||
|
<string name="export_logs">Export logs</string>
|
||||||
<string name="retrieve">Retrieve</string>
|
<string name="retrieve">Retrieve</string>
|
||||||
<string name="wifi_auth_keypair">WiFi keypair</string>
|
<string name="wifi_auth_keypair">WiFi keypair</string>
|
||||||
<string name="keypair">Keypair</string>
|
<string name="keypair">Keypair</string>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ biometric = "1.2.0-alpha05"
|
|||||||
fragment = "1.8.0-beta01"
|
fragment = "1.8.0-beta01"
|
||||||
dhizuku = "2.5.2"
|
dhizuku = "2.5.2"
|
||||||
hiddenApiBypass = "4.3"
|
hiddenApiBypass = "4.3"
|
||||||
|
serialization = "1.7.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
|
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
|
||||||
@@ -28,6 +29,8 @@ dhizuku-api = { module = "io.github.iamr0s:Dhizuku-API", version.ref = "dhizuku"
|
|||||||
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
|
hiddenApiBypass = { module = "org.lsposed.hiddenapibypass:hiddenapibypass", version.ref = "hiddenApiBypass" }
|
||||||
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
|
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "fragment" }
|
||||||
|
|
||||||
|
serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in New Issue
Block a user