Export logs in settings

Set Preferential network config in a new screen
Hide Password entry from home if OwnDroid is not activated, fix #105
This commit is contained in:
BinTianqi
2025-02-22 11:54:21 +08:00
parent 3d6e12581b
commit 8a8d11a89b
11 changed files with 156 additions and 142 deletions

View File

@@ -146,12 +146,12 @@ private fun AppInstaller(
var tab by remember { mutableIntStateOf(0) } var tab by remember { mutableIntStateOf(0) }
val pagerState = rememberPagerState { 2 } val pagerState = rememberPagerState { 2 }
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
tab = pagerState.targetPage
Column(modifier = Modifier.padding(paddingValues)) { Column(modifier = Modifier.padding(paddingValues)) {
TabRow(tab) { TabRow(tab) {
Tab( Tab(
tab == 0, tab == 0,
onClick = { onClick = {
tab = 0
coroutine.launch { scrollState.animateScrollTo(0) } coroutine.launch { scrollState.animateScrollTo(0) }
coroutine.launch { pagerState.animateScrollToPage(0) } coroutine.launch { pagerState.animateScrollToPage(0) }
}, },
@@ -160,7 +160,6 @@ private fun AppInstaller(
Tab( Tab(
tab == 1, tab == 1,
onClick = { onClick = {
tab = 1
coroutine.launch { scrollState.animateScrollTo(0) } coroutine.launch { scrollState.animateScrollTo(0) }
coroutine.launch { pagerState.animateScrollToPage(1) } coroutine.launch { pagerState.animateScrollToPage(1) }
}, },

View File

@@ -66,6 +66,8 @@ import com.bintianqi.owndroid.dpm.AddDelegatedAdmin
import com.bintianqi.owndroid.dpm.AddDelegatedAdminScreen import com.bintianqi.owndroid.dpm.AddDelegatedAdminScreen
import com.bintianqi.owndroid.dpm.AddNetwork import com.bintianqi.owndroid.dpm.AddNetwork
import com.bintianqi.owndroid.dpm.AddNetworkScreen import com.bintianqi.owndroid.dpm.AddNetworkScreen
import com.bintianqi.owndroid.dpm.AddPreferentialNetworkServiceConfig
import com.bintianqi.owndroid.dpm.AddPreferentialNetworkServiceConfigScreen
import com.bintianqi.owndroid.dpm.AffiliationId import com.bintianqi.owndroid.dpm.AffiliationId
import com.bintianqi.owndroid.dpm.AffiliationIdScreen import com.bintianqi.owndroid.dpm.AffiliationIdScreen
import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackage import com.bintianqi.owndroid.dpm.AlwaysOnVpnPackage
@@ -340,7 +342,8 @@ fun Home(activity: FragmentActivity, vm: MyViewModel) {
composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) } composable<RecommendedGlobalProxy> { RecommendedGlobalProxyScreen(::navigateUp) }
composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) } composable<NetworkLogging> { NetworkLoggingScreen(::navigateUp) }
composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) } composable<WifiAuthKeypair> { WifiAuthKeypairScreen(::navigateUp) }
composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp) } composable<PreferentialNetworkService> { PreferentialNetworkServiceScreen(::navigateUp, ::navigate) }
composable<AddPreferentialNetworkServiceConfig> { AddPreferentialNetworkServiceConfigScreen(it.toRoute(), ::navigateUp) }
composable<OverrideApn> { OverrideApnScreen(::navigateUp) } composable<OverrideApn> { OverrideApnScreen(::navigateUp) }
composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) } composable<WorkProfile> { WorkProfileScreen(::navigateUp, ::navigate) }
@@ -507,7 +510,7 @@ private fun HomeScreen(onNavigate: (Any) -> Unit) {
HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) } HomePageItem(R.string.user_restriction, R.drawable.person_off) { onNavigate(UserRestriction) }
} }
HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) } HomePageItem(R.string.users,R.drawable.manage_accounts_fill0) { onNavigate(Users) }
HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) } if(deviceOwner || profileOwner) HomePageItem(R.string.password_and_keyguard, R.drawable.password_fill0) { onNavigate(Password) }
HomePageItem(R.string.settings, R.drawable.settings_fill0) { onNavigate(Settings) } HomePageItem(R.string.settings, R.drawable.settings_fill0) { onNavigate(Settings) }
Spacer(Modifier.padding(vertical = 20.dp)) Spacer(Modifier.padding(vertical = 20.dp))
} }

View File

@@ -4,6 +4,8 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build.VERSION import android.os.Build.VERSION
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -38,16 +40,27 @@ import com.bintianqi.owndroid.ui.MyScaffold
import com.bintianqi.owndroid.ui.SwitchItem import com.bintianqi.owndroid.ui.SwitchItem
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.security.SecureRandom import java.security.SecureRandom
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
@Serializable object Settings @Serializable object Settings
@Composable @Composable
fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) { fun SettingsScreen(onNavigateUp: () -> Unit, onNavigate: (Any) -> Unit) {
val context = LocalContext.current
val exportLogsLauncher = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) {
if(it != null) exportLogs(context, it)
}
MyScaffold(R.string.settings, 0.dp, onNavigateUp) { MyScaffold(R.string.settings, 0.dp, onNavigateUp) {
FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) } FunctionItem(title = R.string.options, icon = R.drawable.tune_fill0) { onNavigate(SettingsOptions) }
FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) } FunctionItem(title = R.string.appearance, icon = R.drawable.format_paint_fill0) { onNavigate(Appearance) }
FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { onNavigate(AuthSettings) } FunctionItem(title = R.string.security, icon = R.drawable.lock_fill0) { onNavigate(AuthSettings) }
FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { onNavigate(ApiSettings) } FunctionItem(title = R.string.api, icon = R.drawable.apps_fill0) { onNavigate(ApiSettings) }
FunctionItem(title = R.string.export_logs, icon = R.drawable.description_fill0) {
val time = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(Date(System.currentTimeMillis()))
exportLogsLauncher.launch("owndroid_log_$time")
}
FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { onNavigate(About) } FunctionItem(title = R.string.about, icon = R.drawable.info_fill0) { onNavigate(About) }
} }
} }

View File

@@ -8,7 +8,9 @@ import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Process
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
@@ -28,6 +30,7 @@ import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.reflect.typeOf import kotlin.reflect.typeOf
var zhCN = true var zhCN = true
@@ -122,3 +125,13 @@ class ChoosePackageContract: ActivityResultContract<Nothing?, String?>() {
override fun parseResult(resultCode: Int, intent: Intent?): String? = override fun parseResult(resultCode: Int, intent: Intent?): String? =
intent?.getStringExtra("package") intent?.getStringExtra("package")
} }
fun exportLogs(context: Context, uri: Uri) {
context.contentResolver.openOutputStream(uri)?.use { output ->
val proc = Runtime.getRuntime().exec("logcat -d")
proc.inputStream.copyTo(output)
if(Build.VERSION.SDK_INT >= 26) proc.waitFor(2L, TimeUnit.SECONDS)
else proc.waitFor()
context.showOperationResultToast(proc.exitValue() == 0)
}
}

View File

@@ -80,7 +80,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.outlined.Delete import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.LocationOn import androidx.compose.material.icons.outlined.LocationOn
@@ -99,6 +98,9 @@ import androidx.compose.material3.MaterialTheme.typography
import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Switch import androidx.compose.material3.Switch
import androidx.compose.material3.Tab import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow import androidx.compose.material3.TabRow
@@ -156,7 +158,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.net.InetAddress import java.net.InetAddress
import kotlin.math.max
import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.jvmErasure
@Serializable object Network @Serializable object Network
@@ -1714,122 +1715,95 @@ fun WifiAuthKeypairScreen(onNavigateUp: () -> Unit) {
@RequiresApi(33) @RequiresApi(33)
@Composable @Composable
fun PreferentialNetworkServiceScreen(onNavigateUp: () -> Unit) { fun PreferentialNetworkServiceScreen(onNavigateUp: () -> Unit, onNavigate: (AddPreferentialNetworkServiceConfig) -> Unit) {
val focusMgr = LocalFocusManager.current
val context = LocalContext.current val context = LocalContext.current
val dpm = context.getDPM() val dpm = context.getDPM()
var masterEnabled by remember { mutableStateOf(false) } var masterEnabled by remember { mutableStateOf(false) }
val configs = remember { mutableStateListOf<PreferentialNetworkServiceConfig>() } val configs = remember { mutableStateListOf<PreferentialNetworkServiceConfig>() }
var index by remember { mutableIntStateOf(-1) }
var enabled by remember { mutableStateOf(false) }
var networkId by remember { mutableStateOf("") }
var allowFallback by remember { mutableStateOf(false) }
var blockNonMatching by remember { mutableStateOf(false) }
var excludedUids by remember { mutableStateOf("") }
var includedUids by remember { mutableStateOf("") }
fun refresh() { fun refresh() {
val config = configs.getOrNull(index)
enabled = config?.isEnabled == true
networkId = config?.networkId?.toString() ?: ""
allowFallback = config?.isFallbackToDefaultConnectionAllowed == true
if(VERSION.SDK_INT >= 34) blockNonMatching = config?.shouldBlockNonMatchingNetworks() == true
includedUids = config?.includedUids?.joinToString("\n") ?: ""
excludedUids = config?.excludedUids?.joinToString("\n") ?: ""
}
fun saveCurrentConfig() {
val builder = PreferentialNetworkServiceConfig.Builder()
builder.setEnabled(enabled)
builder.setNetworkId(networkId.toInt())
builder.setFallbackToDefaultConnectionAllowed(allowFallback)
if(VERSION.SDK_INT >= 34) builder.setShouldBlockNonMatchingNetworks(blockNonMatching)
builder.setIncludedUids(includedUids.lines().dropWhile { it == "" }.map { it.toInt() }.toIntArray())
builder.setExcludedUids(excludedUids.lines().dropWhile { it == "" }.map { it.toInt() }.toIntArray())
if(index < configs.size) configs[index] = builder.build() else configs += builder.build()
}
fun initialize() {
masterEnabled = dpm.isPreferentialNetworkServiceEnabled masterEnabled = dpm.isPreferentialNetworkServiceEnabled
configs.clear()
configs.addAll(dpm.preferentialNetworkServiceConfigs) configs.addAll(dpm.preferentialNetworkServiceConfigs)
index = max(0, configs.size - 1)
refresh()
} }
LaunchedEffect(Unit) { initialize() } LaunchedEffect(Unit) { refresh() }
MyScaffold(R.string.preferential_network_service, 8.dp, onNavigateUp) { MyScaffold(R.string.preferential_network_service, 0.dp, onNavigateUp, false) {
SwitchItem(R.string.enabled, state = masterEnabled, onCheckedChange = { masterEnabled = it }, padding = false) SwitchItem(R.string.enabled, state = masterEnabled, onCheckedChange = {
dpm.isPreferentialNetworkServiceEnabled = it
refresh()
})
Spacer(Modifier.padding(vertical = 4.dp))
configs.forEachIndexed { index, config ->
Row( Row(
horizontalArrangement = Arrangement.SpaceAround, Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 4.dp, bottom = 4.dp),
verticalAlignment = Alignment.CenterVertically, Arrangement.SpaceBetween, Alignment.CenterVertically
modifier = Modifier.fillMaxWidth().padding(top = 8.dp)
) { ) {
IconButton( Column {
onClick = { Text(index.toString())
try {
saveCurrentConfig()
index -= 1
refresh()
} catch(e: Exception) {
e.printStackTrace()
Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show()
} }
}, IconButton({
enabled = index > 0 onNavigate(AddPreferentialNetworkServiceConfig(
enabled = config.isEnabled,
id = config.networkId,
allowFallback = config.isFallbackToDefaultConnectionAllowed,
blockNonMatching = if(VERSION.SDK_INT >= 34) config.shouldBlockNonMatchingNetworks() else false,
excludedUids = config.excludedUids.toList(),
includedUids = config.includedUids.toList(),
index = index
))
}) {
Icon(Icons.Default.Edit, stringResource(R.string.edit))
}
}
}
Row(
Modifier.fillMaxWidth()
.padding(top = 4.dp)
.clickable { onNavigate(AddPreferentialNetworkServiceConfig()) }
.padding(horizontal = 8.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) { ) {
Icon(imageVector = Icons.AutoMirrored.Default.KeyboardArrowLeft, contentDescription = stringResource(R.string.previous)) Icon(Icons.Default.Add, null, Modifier.padding(horizontal = 8.dp))
} Text(stringResource(R.string.add_config))
Text("${index + 1} / ${configs.size}")
IconButton(
onClick = {
try {
saveCurrentConfig()
index += 1
refresh()
} catch(e: Exception) {
e.printStackTrace()
Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show()
} }
} }
) { }
Icon(
imageVector = if(index + 1 >= configs.size) Icons.Default.Add else Icons.AutoMirrored.Default.KeyboardArrowRight, @Serializable data class AddPreferentialNetworkServiceConfig(
contentDescription = stringResource(R.string.previous) val enabled: Boolean = true,
val id: Int = -1,
val allowFallback: Boolean = false,
val blockNonMatching: Boolean = false,
val excludedUids: List<Int> = emptyList(),
val includedUids: List<Int> = emptyList(),
val index: Int = -1
) )
}
} @RequiresApi(33)
Row( @Composable
horizontalArrangement = Arrangement.Center, fun AddPreferentialNetworkServiceConfigScreen(route: AddPreferentialNetworkServiceConfig,onNavigateUp: () -> Unit) {
modifier = Modifier.fillMaxWidth() val updateMode = route.index != -1
) { val context = LocalContext.current
IconButton( val dpm = context.getDPM()
onClick = { var enabled by remember { mutableStateOf(route.enabled) }
try { var id by remember { mutableIntStateOf(route.id) }
saveCurrentConfig() var allowFallback by remember { mutableStateOf(route.allowFallback) }
context.showOperationResultToast(true) var blockNonMatching by remember { mutableStateOf(route.blockNonMatching) }
} catch(e: Exception) { var excludedUids by remember { mutableStateOf(route.excludedUids.joinToString("\n")) }
e.printStackTrace() var includedUids by remember { mutableStateOf(route.includedUids.joinToString("\n")) }
Toast.makeText(context, R.string.failed_to_save_current_config, Toast.LENGTH_SHORT).show() MyScaffold(R.string.preferential_network_service, 8.dp, onNavigateUp, false) {
}
},
modifier = Modifier.padding(end = 10.dp)
) {
Icon(painter = painterResource(R.drawable.save_fill0), contentDescription = stringResource(R.string.save_current_config))
}
IconButton(
onClick = {
if(index < configs.size) configs.removeAt(index)
if(index > 0) index -= 1
refresh()
}
) {
Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(R.string.delete_current_config))
}
}
SwitchItem(title = R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false) SwitchItem(title = R.string.enabled, state = enabled, onCheckedChange = { enabled = it }, padding = false)
OutlinedTextField( AnimatedVisibility(enabled) {
value = networkId, onValueChange = { networkId = it }, Row(verticalAlignment = Alignment.CenterVertically) {
label = { Text(stringResource(R.string.network_id)) }, Text("ID", Modifier.padding(end = 8.dp), style = typography.titleLarge)
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), SingleChoiceSegmentedButtonRow(Modifier.fillMaxWidth()) {
keyboardActions = KeyboardActions { focusMgr.clearFocus() }, for(i in 1..5) {
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) SegmentedButton(id == i, { id = i }, SegmentedButtonDefaults.itemShape(i - 1, 5)) {
) Text(i.toString())
}
}
}
}
}
SwitchItem( SwitchItem(
title = R.string.allow_fallback_to_default_connection, title = R.string.allow_fallback_to_default_connection,
state = allowFallback, onCheckedChange = { allowFallback = it }, padding = false state = allowFallback, onCheckedChange = { allowFallback = it }, padding = false
@@ -1838,28 +1812,66 @@ fun PreferentialNetworkServiceScreen(onNavigateUp: () -> Unit) {
title = R.string.block_non_matching_networks, title = R.string.block_non_matching_networks,
state = blockNonMatching, onCheckedChange = { blockNonMatching = it }, padding = false state = blockNonMatching, onCheckedChange = { blockNonMatching = it }, padding = false
) )
val includedUidsLegal = includedUids.lines().filter { it.isNotBlank() }.let {
it.isEmpty() || (it.all { it.toIntOrNull() != null } && excludedUids.isBlank())
}
OutlinedTextField( OutlinedTextField(
value = includedUids, onValueChange = { includedUids = it }, minLines = 2, value = includedUids, onValueChange = { includedUids = it }, minLines = 2,
label = { Text(stringResource(R.string.included_uids)) }, label = { Text(stringResource(R.string.included_uids)) },
supportingText = { Text(stringResource(R.string.one_uid_per_line)) }, supportingText = { Text(stringResource(R.string.one_uid_per_line)) },
isError = !includedUidsLegal,
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp)
) )
val excludedUidsLegal = excludedUids.lines().filter { it.isNotBlank() }.let {
it.isEmpty() || (it.all { it.toIntOrNull() != null } && includedUids.isBlank())
}
OutlinedTextField( OutlinedTextField(
value = excludedUids, onValueChange = { excludedUids = it }, minLines = 2, value = excludedUids, onValueChange = { excludedUids = it }, minLines = 2,
label = { Text(stringResource(R.string.excluded_uids)) }, label = { Text(stringResource(R.string.excluded_uids)) },
supportingText = { Text(stringResource(R.string.one_uid_per_line)) }, supportingText = { Text(stringResource(R.string.one_uid_per_line)) },
isError = !excludedUidsLegal,
modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp) modifier = Modifier.fillMaxWidth().padding(bottom = 6.dp)
) )
Button( Button(
onClick = { onClick = {
dpm.isPreferentialNetworkServiceEnabled = masterEnabled try {
val config = PreferentialNetworkServiceConfig.Builder().apply {
setEnabled(enabled)
if(enabled) setNetworkId(id.toInt())
setFallbackToDefaultConnectionAllowed(allowFallback)
setExcludedUids(excludedUids.lines().filter { it.isNotBlank() }.map { it.toInt() }.toIntArray())
setIncludedUids(includedUids.lines().filter { it.isNotBlank() }.map { it.toInt() }.toIntArray())
if(VERSION.SDK_INT >= 34) setShouldBlockNonMatchingNetworks(blockNonMatching)
}.build()
val configs = dpm.preferentialNetworkServiceConfigs
if(updateMode) configs[route.index] = config
else configs += config
dpm.preferentialNetworkServiceConfigs = configs dpm.preferentialNetworkServiceConfigs = configs
initialize() onNavigateUp()
context.showOperationResultToast(true) } catch(e: Exception) {
context.showOperationResultToast(false)
e.printStackTrace()
}
}, },
modifier = Modifier.fillMaxWidth().padding(top = 12.dp) enabled = includedUidsLegal && excludedUidsLegal,
modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp)
) { ) {
Text(stringResource(R.string.apply)) Text(stringResource(if(updateMode) R.string.update else R.string.add))
}
if(updateMode) Button(
onClick = {
try {
dpm.preferentialNetworkServiceConfigs = dpm.preferentialNetworkServiceConfigs.drop(route.index)
onNavigateUp()
} catch(e: Exception) {
context.showOperationResultToast(false)
e.printStackTrace()
}
},
colors = ButtonDefaults.buttonColors(MaterialTheme.colorScheme.error, MaterialTheme.colorScheme.onError),
modifier = Modifier.fillMaxWidth()
) {
Text(stringResource(R.string.delete))
} }
} }
} }

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="m313,520 l224,224 -57,56 -320,-320 320,-320 57,56 -224,224h487v80L313,520Z"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M222,760 L80,618l56,-56 85,85 170,-170 56,57 -225,226ZM222,440L80,298l56,-56 85,85 170,-170 56,57 -225,226ZM520,680v-80h360v80L520,680ZM520,360v-80h360v80L520,360Z"/>
</vector>

View File

@@ -311,15 +311,13 @@
<string name="export_logs">Экспортировать журналы</string> <string name="export_logs">Экспортировать журналы</string>
<string name="wifi_auth_keypair">Пара ключей Wi-Fi</string> <string name="wifi_auth_keypair">Пара ключей Wi-Fi</string>
<string name="preferential_network_service">Предпочтительная сетевая служба</string> <string name="preferential_network_service">Предпочтительная сетевая служба</string>
<string name="add_config">Add config</string> <!--TODO-->
<string name="network_id">Идентификатор сети</string> <string name="network_id">Идентификатор сети</string>
<string name="allow_fallback_to_default_connection">Разрешить переход на соединение по умолчанию</string> <string name="allow_fallback_to_default_connection">Разрешить переход на соединение по умолчанию</string>
<string name="block_non_matching_networks">Блокировать несоответствующие сети</string> <string name="block_non_matching_networks">Блокировать несоответствующие сети</string>
<string name="included_uids">Включенные UID</string> <string name="included_uids">Включенные UID</string>
<string name="excluded_uids">Исключенные UID</string> <string name="excluded_uids">Исключенные UID</string>
<string name="one_uid_per_line">Один UID на строку</string> <string name="one_uid_per_line">Один UID на строку</string>
<string name="failed_to_save_current_config">Не удалось сохранить текущую конфигурацию</string>
<string name="save_current_config">Сохранить текущую конфигурацию</string>
<string name="delete_current_config">Удалить текущую конфигурацию</string>
<string name="override_apn_settings">Настройки APN</string> <string name="override_apn_settings">Настройки APN</string>
<string name="total_apn_amount">Количество настроек APN: %1$s</string> <string name="total_apn_amount">Количество настроек APN: %1$s</string>
<string name="select_a_apn_or_create">Выберите настройку APN для редактирования (1~%1$s) или введите 0, чтобы создать новую настройку APN.</string> <string name="select_a_apn_or_create">Выберите настройку APN для редактирования (1~%1$s) или введите 0, чтобы создать новую настройку APN.</string>

View File

@@ -318,15 +318,13 @@
<string name="export_logs">Export logs</string> <!--TODO--> <string name="export_logs">Export logs</string> <!--TODO-->
<string name="wifi_auth_keypair">Wi-Fi anahtar çifti</string> <string name="wifi_auth_keypair">Wi-Fi anahtar çifti</string>
<string name="preferential_network_service">Tercihli ağ hizmeti</string> <string name="preferential_network_service">Tercihli ağ hizmeti</string>
<string name="add_config">Add config</string> <!--TODO-->
<string name="network_id">Network ID</string> <!--TODO--> <string name="network_id">Network ID</string> <!--TODO-->
<string name="allow_fallback_to_default_connection">Allow fallback to default connection</string> <!--TODO--> <string name="allow_fallback_to_default_connection">Allow fallback to default connection</string> <!--TODO-->
<string name="block_non_matching_networks">Block non matching networks</string> <!--TODO--> <string name="block_non_matching_networks">Block non matching networks</string> <!--TODO-->
<string name="included_uids">Included UIDs</string> <!--TODO--> <string name="included_uids">Included UIDs</string> <!--TODO-->
<string name="excluded_uids">Excluded UIDs</string> <!--TODO--> <string name="excluded_uids">Excluded UIDs</string> <!--TODO-->
<string name="one_uid_per_line">One UID per line</string> <!--TODO--> <string name="one_uid_per_line">One UID per line</string> <!--TODO-->
<string name="failed_to_save_current_config">Failed to save current config</string> <!--TODO-->
<string name="save_current_config">Save current config</string> <!--TODO-->
<string name="delete_current_config">Delete current config</string> <!--TODO-->
<string name="override_apn_settings">APN ayarlarını geçersiz kıl</string> <string name="override_apn_settings">APN ayarlarını geçersiz kıl</string>
<string name="total_apn_amount">APN ayarlarının toplamı: %1$s</string> <string name="total_apn_amount">APN ayarlarının toplamı: %1$s</string>
<string name="select_a_apn_or_create">Düzenlemek istediğiniz APN ayarını seçin (1~%1$s) veya yeni bir APN ayarı oluşturmak için 0 girin.</string> <string name="select_a_apn_or_create">Düzenlemek istediğiniz APN ayarını seçin (1~%1$s) veya yeni bir APN ayarı oluşturmak için 0 girin.</string>

View File

@@ -306,15 +306,13 @@
<string name="export_logs">导出日志</string> <string name="export_logs">导出日志</string>
<string name="wifi_auth_keypair">Wi-Fi密钥对</string> <string name="wifi_auth_keypair">Wi-Fi密钥对</string>
<string name="preferential_network_service">首选网络服务</string> <string name="preferential_network_service">首选网络服务</string>
<string name="add_config">添加配置</string>
<string name="network_id">网络ID</string> <string name="network_id">网络ID</string>
<string name="allow_fallback_to_default_connection">允许回落到默认连接</string> <string name="allow_fallback_to_default_connection">允许回落到默认连接</string>
<string name="block_non_matching_networks">阻止不匹配的网络</string> <string name="block_non_matching_networks">阻止不匹配的网络</string>
<string name="included_uids">包含的UID</string> <string name="included_uids">包含的UID</string>
<string name="excluded_uids">排除的UIDs</string> <string name="excluded_uids">排除的UIDs</string>
<string name="one_uid_per_line">一行一个UID</string> <string name="one_uid_per_line">一行一个UID</string>
<string name="failed_to_save_current_config">保存当前配置失败</string>
<string name="save_current_config">保存当前配置</string>
<string name="delete_current_config">删除当前配置</string>
<string name="override_apn_settings">APN设置</string> <string name="override_apn_settings">APN设置</string>
<string name="total_apn_amount">一共有%1$s个APN设置</string> <string name="total_apn_amount">一共有%1$s个APN设置</string>
<string name="select_a_apn_or_create">选择一个你要修改的APN设置(1~%1$s)或者输入0以新建APN设置</string> <string name="select_a_apn_or_create">选择一个你要修改的APN设置(1~%1$s)或者输入0以新建APN设置</string>

View File

@@ -339,15 +339,13 @@
<string name="export_logs">Export logs</string> <string name="export_logs">Export logs</string>
<string name="wifi_auth_keypair">Wi-Fi keypair</string> <string name="wifi_auth_keypair">Wi-Fi keypair</string>
<string name="preferential_network_service">Preferential network service</string> <string name="preferential_network_service">Preferential network service</string>
<string name="add_config">Add config</string>
<string name="network_id">Network ID</string> <string name="network_id">Network ID</string>
<string name="allow_fallback_to_default_connection">Allow fallback to default connection</string> <string name="allow_fallback_to_default_connection">Allow fallback to default connection</string>
<string name="block_non_matching_networks">Block non matching networks</string> <string name="block_non_matching_networks">Block non matching networks</string>
<string name="included_uids">Included UIDs</string> <string name="included_uids">Included UIDs</string>
<string name="excluded_uids">Excluded UIDs</string> <string name="excluded_uids">Excluded UIDs</string>
<string name="one_uid_per_line">One UID per line</string> <string name="one_uid_per_line">One UID per line</string>
<string name="failed_to_save_current_config">Failed to save current config</string>
<string name="save_current_config">Save current config</string>
<string name="delete_current_config">Delete current config</string>
<string name="override_apn_settings">APN settings</string> <string name="override_apn_settings">APN settings</string>
<string name="total_apn_amount">APN settings amount: %1$s</string> <string name="total_apn_amount">APN settings amount: %1$s</string>
<string name="select_a_apn_or_create">Select an APN setting you want to edit (1~%1$s) or enter 0 to create a new APN setting. </string> <string name="select_a_apn_or_create">Select an APN setting you want to edit (1~%1$s) or enter 0 to create a new APN setting. </string>