mirror of
https://github.com/awfixers-stuff/OwnDroid.git
synced 2026-03-23 11:05:59 +00:00
Refactor network logging
This commit is contained in:
@@ -440,7 +440,10 @@ fun Home(vm: MyViewModel, onLock: () -> Unit) {
|
||||
composable<RecommendedGlobalProxy> {
|
||||
RecommendedGlobalProxyScreen(vm::setRecommendedGlobalProxy, ::navigateUp)
|
||||
}
|
||||
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
|
||||
composable<NetworkLogging> {
|
||||
NetworkLoggingScreen(vm::getNetworkLoggingEnabled, vm::setNetworkLoggingEnabled,
|
||||
vm::getNetworkLogsCount, vm::exportNetworkLogs, vm::deleteNetworkLogs, ::navigateUp)
|
||||
}
|
||||
//composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
|
||||
composable<PreferentialNetworkService> {
|
||||
PreferentialNetworkServiceScreen(vm::getPnsEnabled, vm::setPnsEnabled, vm.pnsConfigs,
|
||||
|
||||
@@ -4,7 +4,7 @@ import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 2) {
|
||||
class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 3) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL("CREATE TABLE dhizuku_clients (uid INTEGER PRIMARY KEY," +
|
||||
"signature TEXT, permissions TEXT)")
|
||||
@@ -14,5 +14,12 @@ class MyDbHelper(context: Context): SQLiteOpenHelper(context, "data", null, 2) {
|
||||
db.execSQL("CREATE TABLE security_logs (id INTEGER, tag INTEGER, level INTEGER," +
|
||||
"time INTEGER, data TEXT)")
|
||||
}
|
||||
if (oldVersion < 3) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE network_logs (id INTEGER, package INTEGER, time INTEGER," +
|
||||
"type TEXT, host TEXT, count INTEGER, addresses TEXT, address TEXT," +
|
||||
"port INTEGER)"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,10 @@ import android.database.DatabaseUtils
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.os.Build.VERSION
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.getLongOrNull
|
||||
import androidx.core.database.getStringOrNull
|
||||
import com.bintianqi.owndroid.dpm.NetworkLog
|
||||
import com.bintianqi.owndroid.dpm.SecurityEvent
|
||||
import com.bintianqi.owndroid.dpm.SecurityEventWithData
|
||||
import com.bintianqi.owndroid.dpm.transformSecurityEventData
|
||||
@@ -154,4 +157,71 @@ class MyRepository(val dbHelper: MyDbHelper) {
|
||||
fun deleteSecurityLogs() {
|
||||
dbHelper.writableDatabase.execSQL("DELETE FROM security_logs")
|
||||
}
|
||||
|
||||
fun getNetworkLogsCount(): Long {
|
||||
return DatabaseUtils.queryNumEntries(dbHelper.readableDatabase, "network_logs")
|
||||
}
|
||||
fun writeNetworkLogs(logs: List<NetworkLog>) {
|
||||
val db = dbHelper.writableDatabase
|
||||
val statement = db.compileStatement(
|
||||
"INSERT INTO network_logs VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)
|
||||
db.beginTransaction()
|
||||
logs.forEach { event ->
|
||||
if (event.id == null) statement.bindNull(1)
|
||||
else statement.bindLong(1, event.id)
|
||||
statement.bindString(2, event.packageName)
|
||||
statement.bindLong(3, event.time)
|
||||
statement.bindString(4, event.type)
|
||||
if (event.host == null) statement.bindNull(5)
|
||||
else statement.bindString(5, event.host)
|
||||
if (event.count == null) statement.bindNull(6)
|
||||
else statement.bindLong(6, event.count.toLong())
|
||||
if (event.addresses == null) statement.bindNull(7)
|
||||
else statement.bindString(7, event.addresses.joinToString(","))
|
||||
if (event.address == null) statement.bindNull(8)
|
||||
else statement.bindString(8, event.address)
|
||||
if (event.port == null) statement.bindNull(9)
|
||||
else statement.bindLong(9, event.port.toLong())
|
||||
statement.executeInsert()
|
||||
statement.clearBindings()
|
||||
}
|
||||
db.setTransactionSuccessful()
|
||||
db.endTransaction()
|
||||
statement.close()
|
||||
}
|
||||
fun exportNetworkLogs(stream: OutputStream) {
|
||||
val bw = stream.bufferedWriter()
|
||||
val json = Json {
|
||||
explicitNulls = false
|
||||
}
|
||||
var offset = 0
|
||||
var addComma = false
|
||||
bw.write("[")
|
||||
while (true) {
|
||||
val cursor = dbHelper.readableDatabase.rawQuery(
|
||||
"SELECT * FROM network_logs LIMIT ? OFFSET ?",
|
||||
arrayOf(100.toString(), offset.toString())
|
||||
)
|
||||
if (cursor.count == 0) break
|
||||
while (cursor.moveToNext()) {
|
||||
if (addComma) bw.write(",")
|
||||
addComma = true
|
||||
val log = NetworkLog(
|
||||
cursor.getLongOrNull(0), cursor.getString(1), cursor.getLong(2),
|
||||
cursor.getString(3), cursor.getStringOrNull(4), cursor.getIntOrNull(5),
|
||||
cursor.getStringOrNull(6)?.split(',')?.filter { it.isNotEmpty() },
|
||||
cursor.getStringOrNull(7), cursor.getIntOrNull(8)
|
||||
)
|
||||
bw.write(json.encodeToString(log))
|
||||
offset += 100
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
bw.write("]")
|
||||
bw.close()
|
||||
}
|
||||
fun deleteNetworkLogs() {
|
||||
dbHelper.writableDatabase.execSQL("DELETE FROM network_logs")
|
||||
}
|
||||
}
|
||||
@@ -1753,6 +1753,28 @@ class MyViewModel(application: Application): AndroidViewModel(application) {
|
||||
fun removeApnConfig(id: Int): Boolean {
|
||||
return DPM.removeOverrideApn(DAR, id)
|
||||
}
|
||||
@RequiresApi(26)
|
||||
fun getNetworkLoggingEnabled(): Boolean {
|
||||
return DPM.isNetworkLoggingEnabled(DAR)
|
||||
}
|
||||
@RequiresApi(26)
|
||||
fun setNetworkLoggingEnabled(enabled: Boolean) {
|
||||
DPM.setNetworkLoggingEnabled(DAR, enabled)
|
||||
}
|
||||
fun getNetworkLogsCount(): Int {
|
||||
return myRepo.getNetworkLogsCount().toInt()
|
||||
}
|
||||
fun exportNetworkLogs(uri: Uri, callback: () -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
application.contentResolver.openOutputStream(uri)?.use {
|
||||
myRepo.exportNetworkLogs(it)
|
||||
}
|
||||
withContext(Dispatchers.Main) { callback() }
|
||||
}
|
||||
}
|
||||
fun deleteNetworkLogs() {
|
||||
myRepo.deleteNetworkLogs()
|
||||
}
|
||||
|
||||
@RequiresApi(29)
|
||||
fun getPasswordComplexity(): PasswordComplexity {
|
||||
|
||||
@@ -72,10 +72,15 @@ enum class NotificationType(
|
||||
12, R.string.security_logs_collected, R.drawable.description_fill0,
|
||||
MyNotificationChannel.SecurityLogging
|
||||
),
|
||||
NetworkLogsCollected(
|
||||
13, R.string.network_logs_collected, R.drawable.description_fill0,
|
||||
MyNotificationChannel.NetworkLogging
|
||||
),
|
||||
}
|
||||
|
||||
enum class MyNotificationChannel(val id: String, val text: Int, val importance: Int) {
|
||||
LockTaskMode("LockTaskMode", R.string.lock_task_mode, NotificationManagerCompat.IMPORTANCE_HIGH),
|
||||
Events("Events", R.string.events, NotificationManagerCompat.IMPORTANCE_LOW),
|
||||
SecurityLogging("SecurityLogging", R.string.security_logging, NotificationManagerCompat.IMPORTANCE_MIN)
|
||||
SecurityLogging("SecurityLogging", R.string.security_logging, NotificationManagerCompat.IMPORTANCE_MIN),
|
||||
NetworkLogging("NetworkLogging", R.string.network_logging, NotificationManagerCompat.IMPORTANCE_MIN)
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ fun AppChooserScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
item { Spacer(Modifier.height(60.dp)) }
|
||||
item { Spacer(Modifier.height(BottomPadding)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,9 @@ import android.os.Build.VERSION
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.core.app.NotificationCompat
|
||||
import com.bintianqi.owndroid.dpm.handleNetworkLogs
|
||||
import com.bintianqi.owndroid.dpm.handlePrivilegeChange
|
||||
import com.bintianqi.owndroid.dpm.retrieveNetworkLogs
|
||||
import com.bintianqi.owndroid.dpm.retrieveSecurityLogs
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class Receiver : DeviceAdminReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
@@ -47,10 +44,8 @@ class Receiver : DeviceAdminReceiver() {
|
||||
|
||||
override fun onNetworkLogsAvailable(context: Context, intent: Intent, batchToken: Long, networkLogsCount: Int) {
|
||||
super.onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount)
|
||||
if(VERSION.SDK_INT >= 26) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
handleNetworkLogs(context, batchToken)
|
||||
}
|
||||
if (VERSION.SDK_INT >= 26) {
|
||||
retrieveNetworkLogs(context.applicationContext as MyApplication, batchToken)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ fun ApplicationDetailsScreen(
|
||||
)
|
||||
if(VERSION.SDK_INT >= 28) FunctionItem(R.string.clear_app_storage, icon = R.drawable.mop_fill0) { dialog = 1 }
|
||||
FunctionItem(R.string.uninstall, icon = R.drawable.delete_fill0) { dialog = 2 }
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
if(dialog == 1 && VERSION.SDK_INT >= 28)
|
||||
ClearAppStorageDialog(packageName, vm::clearAppData) { dialog = 0 }
|
||||
@@ -606,7 +606,7 @@ fun CredentialManagerPolicyScreen(
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,7 +661,7 @@ fun PermittedAsAndImPackages(
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Notes(note, HorizontalPadding)
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -771,7 +771,7 @@ fun PackageFunctionScreen(
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
if (notes != null) Notes(notes, HorizontalPadding)
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,12 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.add
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
import kotlinx.serialization.json.putJsonArray
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
fun binderWrapperDevicePolicyManager(appContext: Context): DevicePolicyManager? {
|
||||
@@ -137,39 +132,37 @@ val runtimePermissions = listOf(
|
||||
PermissionItem(Manifest.permission.ACTIVITY_RECOGNITION, R.string.permission_ACTIVITY_RECOGNITION, R.drawable.history_fill0, true, 29)
|
||||
).filter { VERSION.SDK_INT >= it.requiresApi }
|
||||
|
||||
@Serializable
|
||||
class NetworkLog(
|
||||
val id: Long?, @SerialName("package") val packageName: String, val time: Long, val type: String,
|
||||
val host: String?, val count: Int?, val addresses: List<String>?,
|
||||
val address: String?, val port: Int?
|
||||
)
|
||||
|
||||
@RequiresApi(26)
|
||||
fun handleNetworkLogs(context: Context, batchToken: Long) {
|
||||
val networkEvents = Privilege.DPM.retrieveNetworkLogs(Privilege.DAR, batchToken) ?: return
|
||||
val file = context.filesDir.resolve("NetworkLogs.json")
|
||||
val fileExist = file.exists()
|
||||
val json = Json { ignoreUnknownKeys = true; explicitNulls = false }
|
||||
val buffer = file.bufferedWriter()
|
||||
networkEvents.forEachIndexed { index, event ->
|
||||
if(fileExist && index == 0) buffer.write(",")
|
||||
val item = buildJsonObject {
|
||||
if(VERSION.SDK_INT >= 28) put("id", event.id)
|
||||
put("time", event.timestamp)
|
||||
put("package", event.packageName)
|
||||
if(event is DnsEvent) {
|
||||
put("type", "dns")
|
||||
put("host", event.hostname)
|
||||
put("count", event.totalResolvedAddressCount)
|
||||
putJsonArray("addresses") {
|
||||
event.inetAddresses.forEach { inetAddresses ->
|
||||
add(inetAddresses.hostAddress)
|
||||
fun retrieveNetworkLogs(app: MyApplication, token: Long) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val logs = Privilege.DPM.retrieveNetworkLogs(Privilege.DAR, token)?.mapNotNull {
|
||||
when (it) {
|
||||
is DnsEvent -> NetworkLog(
|
||||
if (VERSION.SDK_INT >= 28) it.id else null, it.packageName, it.timestamp, "dns",
|
||||
it.hostname, it.totalResolvedAddressCount,
|
||||
it.inetAddresses.mapNotNull { address -> address.hostAddress }, null, null
|
||||
)
|
||||
is ConnectEvent -> NetworkLog(
|
||||
if (VERSION.SDK_INT >= 28) it.id else null, it.packageName, it.timestamp,
|
||||
"connect", null, null, null, it.inetAddress.hostAddress, it.port
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
if (logs.isNullOrEmpty()) return@launch
|
||||
app.myRepo.writeNetworkLogs(logs)
|
||||
NotificationUtils.sendBasicNotification(
|
||||
app, NotificationType.NetworkLogsCollected,
|
||||
app.getString(R.string.n_logs_in_total, logs.size)
|
||||
)
|
||||
}
|
||||
if(event is ConnectEvent) {
|
||||
put("type", "connect")
|
||||
put("address", event.inetAddress.hostAddress)
|
||||
put("port", event.port)
|
||||
}
|
||||
}
|
||||
buffer.write(json.encodeToString(item))
|
||||
if(index < networkEvents.size - 1) buffer.write(",")
|
||||
}
|
||||
buffer.close()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@@ -493,7 +486,8 @@ fun transformSecurityEventData(tag: Int, payload: Any): SecurityEventData? {
|
||||
@RequiresApi(24)
|
||||
fun retrieveSecurityLogs(app: MyApplication) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR) ?: return@launch
|
||||
val logs = Privilege.DPM.retrieveSecurityLogs(Privilege.DAR)
|
||||
if (logs.isNullOrEmpty()) return@launch
|
||||
app.myRepo.writeSecurityLogs(logs)
|
||||
NotificationUtils.sendBasicNotification(
|
||||
app, NotificationType.SecurityLogsCollected,
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.app.admin.DevicePolicyManager.WIFI_SECURITY_PERSONAL
|
||||
import android.app.admin.WifiSsidPolicy
|
||||
import android.app.usage.NetworkStats
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.net.wifi.WifiConfiguration
|
||||
import android.os.Build.VERSION
|
||||
import android.provider.Telephony
|
||||
@@ -106,6 +107,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.BottomPadding
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
@@ -116,6 +118,7 @@ import com.bintianqi.owndroid.formatFileSize
|
||||
import com.bintianqi.owndroid.adaptiveInsets
|
||||
import com.bintianqi.owndroid.popToast
|
||||
import com.bintianqi.owndroid.showOperationResultToast
|
||||
import com.bintianqi.owndroid.ui.CircularProgressDialog
|
||||
import com.bintianqi.owndroid.ui.ErrorDialog
|
||||
import com.bintianqi.owndroid.ui.FullWidthCheckBoxItem
|
||||
import com.bintianqi.owndroid.ui.FullWidthRadioButtonItem
|
||||
@@ -135,6 +138,9 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@Serializable object Network
|
||||
|
||||
@@ -803,7 +809,7 @@ private fun AddNetworkScreen(
|
||||
) {
|
||||
Text(stringResource(if (updating) R.string.update else R.string.add))
|
||||
}
|
||||
Spacer(Modifier.height(60.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1539,50 +1545,82 @@ fun RecommendedGlobalProxyScreen(
|
||||
|
||||
@RequiresApi(26)
|
||||
@Composable
|
||||
fun NetworkLoggingScreen(onNavigateUp: () -> Unit) {
|
||||
fun NetworkLoggingScreen(
|
||||
getEnabled: () -> Boolean, setEnabled: (Boolean) -> Unit, getCount: () -> Int,
|
||||
exportLogs: (Uri, () -> Unit) -> Unit, deleteLogs: () -> Unit, onNavigateUp: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val logFile = context.filesDir.resolve("NetworkLogs.json")
|
||||
var fileSize by remember { mutableLongStateOf(0) }
|
||||
LaunchedEffect(Unit) { fileSize = logFile.length() }
|
||||
val exportNetworkLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri ->
|
||||
if(uri != null) context.contentResolver.openOutputStream(uri)?.use { outStream ->
|
||||
outStream.write("[".encodeToByteArray())
|
||||
logFile.inputStream().use { it.copyTo(outStream) }
|
||||
outStream.write("]".encodeToByteArray())
|
||||
var enabled by remember { mutableStateOf(false) }
|
||||
var count by remember { mutableIntStateOf(0) }
|
||||
var dialog by rememberSaveable { mutableStateOf(false) }
|
||||
var exporting by rememberSaveable { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
enabled = getEnabled()
|
||||
count = getCount()
|
||||
}
|
||||
val exportLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/json")
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
exporting = true
|
||||
exportLogs(uri) {
|
||||
exporting = false
|
||||
context.showOperationResultToast(true)
|
||||
}
|
||||
}
|
||||
MyScaffold(R.string.network_logging, onNavigateUp) {
|
||||
}
|
||||
MyScaffold(R.string.network_logging, onNavigateUp, 0.dp) {
|
||||
SwitchItem(
|
||||
R.string.enable,
|
||||
getState = { Privilege.DPM.isNetworkLoggingEnabled(Privilege.DAR) },
|
||||
onCheckedChange = { Privilege.DPM.setNetworkLoggingEnabled(Privilege.DAR, it) },
|
||||
padding = false
|
||||
R.string.enable, enabled, {
|
||||
setEnabled(it)
|
||||
enabled = it
|
||||
}
|
||||
)
|
||||
Text(
|
||||
stringResource(R.string.n_logs_in_total, count),
|
||||
Modifier.padding(HorizontalPadding, 5.dp)
|
||||
)
|
||||
Text(stringResource(R.string.log_file_size_is, formatFileSize(fileSize)))
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) {
|
||||
Button(
|
||||
onClick = {
|
||||
exportNetworkLogsLauncher.launch("NetworkLogs.json")
|
||||
{
|
||||
val date = SimpleDateFormat("yyyyMMdd", Locale.getDefault()).format(Date())
|
||||
exportLauncher.launch("network_logs_$date")
|
||||
},
|
||||
enabled = fileSize > 0,
|
||||
modifier = Modifier.fillMaxWidth(0.49F)
|
||||
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||
count > 0
|
||||
) {
|
||||
Text(stringResource(R.string.export_logs))
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
logFile.delete()
|
||||
fileSize = logFile.length()
|
||||
if (count > 0) Button(
|
||||
{
|
||||
dialog = true
|
||||
},
|
||||
enabled = fileSize > 0,
|
||||
modifier = Modifier.fillMaxWidth(0.96F)
|
||||
Modifier.fillMaxWidth().padding(horizontal = HorizontalPadding),
|
||||
) {
|
||||
Text(stringResource(R.string.delete_logs))
|
||||
}
|
||||
Spacer(Modifier.height(10.dp))
|
||||
Notes(R.string.info_network_log, HorizontalPadding)
|
||||
}
|
||||
Notes(R.string.info_network_log)
|
||||
if (exporting) CircularProgressDialog { exporting = false }
|
||||
if (dialog) AlertDialog(
|
||||
text = {
|
||||
Text(stringResource(R.string.delete_logs))
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton({
|
||||
deleteLogs()
|
||||
dialog = false
|
||||
}) {
|
||||
Text(stringResource(R.string.confirm))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton({ dialog = false }) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { dialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable object PreferentialNetworkService
|
||||
|
||||
@@ -81,6 +81,7 @@ import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.AppInfo
|
||||
import com.bintianqi.owndroid.BottomPadding
|
||||
import com.bintianqi.owndroid.DhizukuClientInfo
|
||||
import com.bintianqi.owndroid.DhizukuPermissions
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
@@ -615,7 +616,7 @@ fun AddDelegatedAdminScreen(
|
||||
) {
|
||||
Text(stringResource(R.string.delete))
|
||||
}
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,6 +101,7 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bintianqi.owndroid.AppInfo
|
||||
import com.bintianqi.owndroid.BottomPadding
|
||||
import com.bintianqi.owndroid.HorizontalPadding
|
||||
import com.bintianqi.owndroid.MyViewModel
|
||||
import com.bintianqi.owndroid.Privilege
|
||||
@@ -1281,7 +1282,7 @@ private fun LockTaskPackages(
|
||||
Text(stringResource(R.string.add))
|
||||
}
|
||||
Notes(R.string.info_lock_task_packages)
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1331,7 +1332,7 @@ private fun LockTaskFeatures(
|
||||
) {
|
||||
Text(stringResource(R.string.apply))
|
||||
}
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
ErrorDialog(errorMessage) { errorMessage = null }
|
||||
}
|
||||
}
|
||||
@@ -1417,7 +1418,7 @@ fun CaCertScreen(
|
||||
HorizontalDivider()
|
||||
}
|
||||
item {
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
if (selectedCaCert != null && (dialog == 1 || dialog == 2)) {
|
||||
|
||||
@@ -239,7 +239,7 @@ fun UserRestrictionEditorScreen(
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Ascii, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions { add() }
|
||||
)
|
||||
Spacer(Modifier.height(40.dp))
|
||||
Spacer(Modifier.height(BottomPadding))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,7 +277,6 @@
|
||||
<string name="invalid_config">Неверная конфигурация</string>
|
||||
<string name="excluded_hosts">Исключить хосты</string>
|
||||
<string name="network_logging">Сетевой журнал</string>
|
||||
<string name="log_file_size_is">Размер файла журнала: %1$s</string>
|
||||
<string name="delete_logs">Удалить журналы</string>
|
||||
<string name="export_logs">Экспортировать журналы</string>
|
||||
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
<string name="invalid_config">Geçersiz yapılandırma</string>
|
||||
<string name="excluded_hosts">Hariç tutulan ana bilgisayarlar</string>
|
||||
<string name="network_logging">Ağ Kayıtları</string>
|
||||
<string name="log_file_size_is">Kayıt dosyası boyutu: %1$s</string>
|
||||
<string name="delete_logs">Kayıtları Sil</string>
|
||||
<string name="export_logs">Kayıtları Dışa Aktar</string>
|
||||
<string name="wifi_auth_keypair">Wi-Fi Kimlik Doğrulama Anahtar Çifti</string>
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
<string name="invalid_config">无效配置</string>
|
||||
<string name="excluded_hosts">排除的主机</string>
|
||||
<string name="network_logging">网络日志</string>
|
||||
<string name="log_file_size_is">日志文件大小:%1$s</string>
|
||||
<string name="network_logs_collected">网络日志已收集</string>
|
||||
<string name="delete_logs">删除日志</string>
|
||||
<string name="export_logs">导出日志</string>
|
||||
<string name="wifi_auth_keypair">Wi-Fi密钥对</string>
|
||||
|
||||
@@ -325,7 +325,7 @@
|
||||
<string name="invalid_config">Invalid config</string>
|
||||
<string name="excluded_hosts">Excluded hosts</string>
|
||||
<string name="network_logging">Network logging</string>
|
||||
<string name="log_file_size_is">Log file size: %1$s</string>
|
||||
<string name="network_logs_collected">Network logs collected</string>
|
||||
<string name="delete_logs">Delete logs</string>
|
||||
<string name="export_logs">Export logs</string>
|
||||
<string name="wifi_auth_keypair">Wi-Fi keypair</string>
|
||||
|
||||
Reference in New Issue
Block a user