lint fixes
Some checks are pending
CI / build (push) Waiting to run

This commit is contained in:
xtclovver 2026-04-12 22:55:09 +03:00
parent 90e46df80d
commit ce55eaef0e
19 changed files with 240 additions and 138 deletions

2
.idea/misc.xml generated
View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View file

@ -101,8 +101,8 @@ dependencies {
implementation(libs.okhttp.dnsoverhttps)
testImplementation(libs.junit)
testImplementation(libs.okhttp.mockwebserver)
testImplementation("org.robolectric:robolectric:4.14.1")
testImplementation("androidx.test:core:1.6.1")
testImplementation(libs.robolectric)
testImplementation(libs.androidx.test.core)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
}

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -8,7 +9,6 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<queries>
@ -51,7 +51,8 @@
android:supportsRtl="true"
android:theme="@style/Theme.RKNHardering"
android:networkSecurityConfig="@xml/network_security_config"
android:localeConfig="@xml/locales_config">
android:localeConfig="@xml/locales_config"
tools:targetApi="tiramisu">
<activity
android:name=".MainActivity"
android:exported="true">

View file

@ -11,21 +11,22 @@ import android.os.Bundle
import android.provider.Settings
import android.util.TypedValue
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.core.content.ContextCompat
import androidx.core.text.BidiFormatter
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isNotEmpty
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
@ -77,7 +78,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var btnRunCheck: MaterialButton
private lateinit var btnStopCheck: MaterialButton
private lateinit var cardRunCheckNotice: MaterialCardView
private lateinit var resultsScrollView: ScrollView
private lateinit var resultsScrollView: TouchAwareScrollView
private lateinit var textCheckStatus: TextView
private lateinit var viewModel: CheckViewModel
private var hasDismissedRunCheckNotice = false
@ -143,7 +144,7 @@ class MainActivity : AppCompatActivity() {
ActivityResultContracts.RequestMultiplePermissions(),
) { result ->
markPermissionsRequested(result.keys)
prefs.edit().putBoolean(PREF_RATIONALE_SHOWN, true).apply()
prefs.edit { putBoolean(PREF_RATIONALE_SHOWN, true) }
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -243,14 +244,8 @@ class MainActivity : AppCompatActivity() {
}
private fun setupResultsScrollTracking() {
resultsScrollView.setOnTouchListener { _, event ->
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> userTouchScrollInProgress = true
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
-> userTouchScrollInProgress = false
}
false
resultsScrollView.onUserTouchChanged = { isTouching ->
userTouchScrollInProgress = isTouching
}
resultsScrollView.setOnScrollChangeListener { _, _, _, _, _ ->
if (userTouchScrollInProgress && !isAutoScrollInProgress) {
@ -282,7 +277,7 @@ class MainActivity : AppCompatActivity() {
launchPermissionRequest(permissions)
}
.setNegativeButton(getString(R.string.main_perm_skip)) { _, _ ->
prefs.edit().putBoolean(PREF_RATIONALE_SHOWN, true).apply()
prefs.edit { putBoolean(PREF_RATIONALE_SHOWN, true) }
}
.setCancelable(false)
.show()
@ -348,7 +343,7 @@ class MainActivity : AppCompatActivity() {
?.toMutableSet()
?: mutableSetOf()
requested.addAll(permissions)
prefs.edit().putStringSet(PREF_REQUESTED_PERMISSIONS, requested).apply()
prefs.edit { putStringSet(PREF_REQUESTED_PERMISSIONS, requested) }
}
private fun hasPermissionBeenRequested(permission: String): Boolean {
@ -630,7 +625,7 @@ class MainActivity : AppCompatActivity() {
private fun showLoadingCardForStage(stage: RunningStage) {
if (stage in completedStages) return
if (stage in loadingStages && cardForStage(stage).visibility == View.VISIBLE) return
if (stage in loadingStages && cardForStage(stage).isVisible) return
loadingStages += stage
when (stage) {
@ -855,7 +850,11 @@ class MainActivity : AppCompatActivity() {
else -> "..."
}
loadingStages.forEach { stage ->
statusViewForStage(stage).text = stageLoadingStatusBase(stage) + dots
statusViewForStage(stage).text = getString(
R.string.main_loading_status_progress,
stageLoadingStatusBase(stage),
dots,
)
}
}
@ -911,7 +910,7 @@ class MainActivity : AppCompatActivity() {
animate: Boolean = true,
shouldAutoScroll: Boolean = false,
) {
val wasVisible = card.visibility == View.VISIBLE
val wasVisible = card.isVisible
if (!wasVisible) {
card.animate().cancel()
card.visibility = View.VISIBLE
@ -1498,7 +1497,7 @@ class MainActivity : AppCompatActivity() {
content = narrative.reasonRows.map(::createVerdictBulletView),
)
val hasDetails = verdictDetailsContent.childCount > 0
val hasDetails = verdictDetailsContent.isNotEmpty()
verdictDetailsDivider.visibility = if (hasDetails) View.VISIBLE else View.GONE
btnVerdictDetails.visibility = if (hasDetails) View.VISIBLE else View.GONE
verdictDetailsContent.visibility = if (hasDetails && isVerdictDetailsExpanded) View.VISIBLE else View.GONE
@ -1508,7 +1507,7 @@ class MainActivity : AppCompatActivity() {
private fun addVerdictSection(title: String, content: List<View>) {
if (content.isEmpty()) return
if (verdictDetailsContent.childCount > 0) {
if (verdictDetailsContent.isNotEmpty()) {
verdictDetailsContent.addView(
View(this).apply {
layoutParams = LinearLayout.LayoutParams(

View file

@ -2,7 +2,6 @@ package com.notcvnt.rknhardering
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
@ -11,6 +10,8 @@ import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.widget.doAfterTextChanged
@ -26,6 +27,7 @@ import com.notcvnt.rknhardering.network.DnsResolverPreset
import com.notcvnt.rknhardering.network.DnsResolverPresets
import com.notcvnt.rknhardering.probe.ProxyScanner
import java.text.NumberFormat
import java.util.Locale
class SettingsActivity : AppCompatActivity() {
@ -115,8 +117,8 @@ class SettingsActivity : AppCompatActivity() {
chipGroupPortRange.check(chipId)
customPortRangeContainer.visibility = if (portRange == "custom") View.VISIBLE else View.GONE
editPortStart.setText(prefs.getInt(PREF_PORT_RANGE_START, 1024).toString())
editPortEnd.setText(prefs.getInt(PREF_PORT_RANGE_END, 65535).toString())
editPortStart.setText(formatPortInputValue(prefs.getInt(PREF_PORT_RANGE_START, 1024)))
editPortEnd.setText(formatPortInputValue(prefs.getInt(PREF_PORT_RANGE_END, 65535)))
updatePortRangePreview()
loadResolverSettings()
@ -142,7 +144,7 @@ class SettingsActivity : AppCompatActivity() {
private fun setupListeners() {
switchSplitTunnel.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean(PREF_SPLIT_TUNNEL_ENABLED, isChecked).apply()
prefs.edit { putBoolean(PREF_SPLIT_TUNNEL_ENABLED, isChecked) }
updatePortRangeEnabled(isChecked)
}
@ -152,7 +154,7 @@ class SettingsActivity : AppCompatActivity() {
.setTitle(R.string.settings_network_disable_title)
.setMessage(R.string.settings_network_disable_message)
.setPositiveButton(R.string.settings_network_disable_confirm) { _, _ ->
prefs.edit().putBoolean(PREF_NETWORK_REQUESTS_ENABLED, false).apply()
prefs.edit { putBoolean(PREF_NETWORK_REQUESTS_ENABLED, false) }
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
switchNetworkRequests.isChecked = true
@ -162,12 +164,12 @@ class SettingsActivity : AppCompatActivity() {
}
.show()
} else {
prefs.edit().putBoolean(PREF_NETWORK_REQUESTS_ENABLED, true).apply()
prefs.edit { putBoolean(PREF_NETWORK_REQUESTS_ENABLED, true) }
}
}
switchPrivacyMode.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean(PREF_PRIVACY_MODE, isChecked).apply()
prefs.edit { putBoolean(PREF_PRIVACY_MODE, isChecked) }
}
chipGroupPortRange.setOnCheckedStateChangeListener { _, checkedIds ->
@ -179,7 +181,7 @@ class SettingsActivity : AppCompatActivity() {
R.id.chipPortCustom -> "custom"
else -> "full"
}
prefs.edit().putString(PREF_PORT_RANGE, value).apply()
prefs.edit { putString(PREF_PORT_RANGE, value) }
customPortRangeContainer.visibility = if (value == "custom") View.VISIBLE else View.GONE
updatePortRangePreview()
}
@ -187,18 +189,18 @@ class SettingsActivity : AppCompatActivity() {
chipGroupResolverMode.setOnCheckedStateChangeListener { _, checkedIds ->
if (checkedIds.isEmpty()) return@setOnCheckedStateChangeListener
saveCustomResolverFields()
prefs.edit()
.putString(PREF_DNS_RESOLVER_MODE, selectedResolverMode().prefValue)
.apply()
prefs.edit {
putString(PREF_DNS_RESOLVER_MODE, selectedResolverMode().prefValue)
}
refreshResolverUi(restoreCustomValues = true)
}
chipGroupResolverPreset.setOnCheckedStateChangeListener { _, checkedIds ->
if (checkedIds.isEmpty()) return@setOnCheckedStateChangeListener
saveCustomResolverFields()
prefs.edit()
.putString(PREF_DNS_RESOLVER_PRESET, selectedResolverPreset().prefValue)
.apply()
prefs.edit {
putString(PREF_DNS_RESOLVER_PRESET, selectedResolverPreset().prefValue)
}
refreshResolverUi(restoreCustomValues = true)
}
@ -231,7 +233,7 @@ class SettingsActivity : AppCompatActivity() {
R.id.chipThemeDark -> "dark"
else -> "system"
}
prefs.edit().putString(PREF_THEME, value).apply()
prefs.edit { putString(PREF_THEME, value) }
applyTheme(value)
}
@ -244,7 +246,7 @@ class SettingsActivity : AppCompatActivity() {
R.id.chipLangZh -> "zh-CN"
else -> ""
}
prefs.edit().putString(PREF_LANGUAGE, value).commit()
prefs.edit { putString(PREF_LANGUAGE, value) }
AppUiSettings.applyLanguage(value)
}
@ -253,7 +255,7 @@ class SettingsActivity : AppCompatActivity() {
}
findViewById<MaterialCardView>(R.id.cardGithub).setOnClickListener {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github_repo_url))))
startActivity(Intent(Intent.ACTION_VIEW, getString(R.string.github_repo_url).toUri()))
}
}
@ -276,12 +278,12 @@ class SettingsActivity : AppCompatActivity() {
val end = editPortEnd.text.toString().toIntOrNull()?.coerceIn(1024, 65535) ?: 65535
val validStart = minOf(start, end)
val validEnd = maxOf(start, end)
prefs.edit()
.putInt(PREF_PORT_RANGE_START, validStart)
.putInt(PREF_PORT_RANGE_END, validEnd)
.apply()
editPortStart.setText(validStart.toString())
editPortEnd.setText(validEnd.toString())
prefs.edit {
putInt(PREF_PORT_RANGE_START, validStart)
putInt(PREF_PORT_RANGE_END, validEnd)
}
editPortStart.setText(formatPortInputValue(validStart))
editPortEnd.setText(formatPortInputValue(validEnd))
}
private fun updatePortRangePreview() {
@ -390,11 +392,15 @@ class SettingsActivity : AppCompatActivity() {
private fun saveCustomResolverFields() {
// Presets reuse the same text fields for display, so only persist while custom is active.
if (persistedResolverPreset() != DnsResolverPreset.CUSTOM) return
prefs.edit()
.putString(PREF_DNS_RESOLVER_DIRECT_SERVERS, editResolverDirectServers.text?.toString().orEmpty().trim())
.putString(PREF_DNS_RESOLVER_DOH_URL, editResolverDohUrl.text?.toString().orEmpty().trim())
.putString(PREF_DNS_RESOLVER_DOH_BOOTSTRAP, editResolverBootstrap.text?.toString().orEmpty().trim())
.apply()
prefs.edit {
putString(PREF_DNS_RESOLVER_DIRECT_SERVERS, editResolverDirectServers.text?.toString().orEmpty().trim())
putString(PREF_DNS_RESOLVER_DOH_URL, editResolverDohUrl.text?.toString().orEmpty().trim())
putString(PREF_DNS_RESOLVER_DOH_BOOTSTRAP, editResolverBootstrap.text?.toString().orEmpty().trim())
}
}
private fun formatPortInputValue(value: Int): String {
return String.format(Locale.US, "%d", value)
}
private fun persistedResolverPreset(): DnsResolverPreset {

View file

@ -0,0 +1,47 @@
package com.notcvnt.rknhardering
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.ScrollView
class TouchAwareScrollView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : ScrollView(context, attrs, defStyleAttr) {
var onUserTouchChanged: ((Boolean) -> Unit)? = null
private var isUserTouchActive = false
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
updateTouchState(ev)
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
updateTouchState(ev)
if (ev.actionMasked == MotionEvent.ACTION_UP) {
performClick()
}
return super.onTouchEvent(ev)
}
override fun performClick(): Boolean {
return super.performClick()
}
private fun updateTouchState(event: MotionEvent) {
val nextState = when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> true
MotionEvent.ACTION_UP,
MotionEvent.ACTION_CANCEL,
-> false
else -> isUserTouchActive
}
if (nextState == isUserTouchActive) return
isUserTouchActive = nextState
onUserTouchChanged?.invoke(nextState)
}
}

View file

@ -399,11 +399,13 @@ object BypassChecker {
)
}
if (xrayApiScanResult.outbounds.size > 10) {
val extraOutboundsCount = xrayApiScanResult.outbounds.size - 10
findings.add(
Finding(
description = context.getString(
R.string.checker_bypass_extra_outbounds,
xrayApiScanResult.outbounds.size - 10,
description = context.resources.getQuantityString(
R.plurals.checker_bypass_extra_outbounds,
extraOutboundsCount,
extraOutboundsCount,
),
detected = true,
source = EvidenceSource.XRAY_API,

View file

@ -177,7 +177,7 @@ object IndirectSignsChecker {
return try {
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetwork = cm.activeNetwork
cm.allNetworks.mapNotNull { network ->
allNetworksSnapshot(cm).mapNotNull { network ->
val caps = cm.getNetworkCapabilities(network) ?: return@mapNotNull null
val linkProperties = cm.getLinkProperties(network) ?: return@mapNotNull null
if (!caps.hasCapability(android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
@ -217,6 +217,11 @@ object IndirectSignsChecker {
}
}
@Suppress("DEPRECATION")
private fun allNetworksSnapshot(cm: ConnectivityManager): Array<android.net.Network> {
return cm.allNetworks
}
private fun checkNotVpnCapability(
context: Context,
findings: MutableList<Finding>,

View file

@ -160,9 +160,11 @@ object LocationSignalsChecker {
}
private fun collectSimCards(context: Context, tm: TelephonyManager): List<SimCardInfo> {
val sm = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
as? android.telephony.SubscriptionManager
val subscriptions = runCatching { sm?.activeSubscriptionInfoList }.getOrNull()
val subscriptions = if (hasPermission(context, Manifest.permission.READ_PHONE_STATE)) {
getActiveSubscriptions(context)
} else {
null
}
if (!subscriptions.isNullOrEmpty()) {
return subscriptions.mapNotNull { info ->
@ -203,6 +205,13 @@ object LocationSignalsChecker {
}.getOrElse { emptyList() }
}
@Suppress("MissingPermission")
private fun getActiveSubscriptions(context: Context): List<android.telephony.SubscriptionInfo>? {
val subscriptionManager = context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)
as? android.telephony.SubscriptionManager
return runCatching { subscriptionManager?.activeSubscriptionInfoList }.getOrNull()
}
private suspend fun collectCellCandidates(
context: Context,
tm: TelephonyManager,
@ -387,7 +396,7 @@ object LocationSignalsChecker {
private fun toWifiLookupCandidate(scanResult: ScanResult): WifiLookupCandidate? {
val macAddress = normalizeMacAddress(scanResult.BSSID) ?: return null
val ssid = normalizeSsid(scanResult.SSID) ?: return null
val ssid = normalizeSsid(scanResultSsid(scanResult)) ?: return null
if (ssid.endsWith("_nomap", ignoreCase = true)) return null
return WifiLookupCandidate(
@ -408,6 +417,16 @@ object LocationSignalsChecker {
?.let(::normalizeOperatorCode)
}
private fun scanResultSsid(scanResult: ScanResult): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Api33Impl.scanResultSsid(scanResult)
} else {
@Suppress("DEPRECATION")
scanResult.SSID
}
}
@Suppress("DEPRECATION")
private fun gsmMcc(identity: android.telephony.CellIdentityGsm): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.gsmMcc(identity)
@ -416,6 +435,7 @@ object LocationSignalsChecker {
}
}
@Suppress("DEPRECATION")
private fun gsmMnc(identity: android.telephony.CellIdentityGsm): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.gsmMnc(identity)
@ -424,6 +444,7 @@ object LocationSignalsChecker {
}
}
@Suppress("DEPRECATION")
private fun lteMcc(identity: android.telephony.CellIdentityLte): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.lteMcc(identity)
@ -432,6 +453,7 @@ object LocationSignalsChecker {
}
}
@Suppress("DEPRECATION")
private fun lteMnc(identity: android.telephony.CellIdentityLte): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.lteMnc(identity)
@ -440,6 +462,7 @@ object LocationSignalsChecker {
}
}
@Suppress("DEPRECATION")
private fun wcdmaMcc(identity: android.telephony.CellIdentityWcdma): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.wcdmaMcc(identity)
@ -448,6 +471,7 @@ object LocationSignalsChecker {
}
}
@Suppress("DEPRECATION")
private fun wcdmaMnc(identity: android.telephony.CellIdentityWcdma): String? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
Api28Impl.wcdmaMnc(identity)
@ -490,6 +514,16 @@ object LocationSignalsChecker {
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private object Api33Impl {
@DoNotInline
fun scanResultSsid(scanResult: ScanResult): String? {
return scanResult.wifiSsid
?.toString()
?.trim('"')
}
}
private fun normalizeCellValue(value: Int): Long? {
return value.toLong().takeIf { it in 0 until Int.MAX_VALUE.toLong() }
}

View file

@ -6,7 +6,6 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
@ -24,7 +23,7 @@
app:menu="@menu/menu_main" />
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
<com.notcvnt.rknhardering.TouchAwareScrollView
android:id="@+id/resultsScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -136,13 +135,14 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="GeoIP"
android:text="@string/main_card_geo_ip"
android:textColor="?android:attr/textColorPrimary"
android:textSize="18sp"
android:textStyle="bold" />
@ -205,7 +205,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
@ -266,7 +267,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
@ -335,7 +337,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
@ -388,7 +391,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
@ -457,13 +461,14 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:contentDescription="status" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Split tunnel bypass"
android:text="@string/settings_split_tunnel"
android:textColor="?android:attr/textColorPrimary"
android:textSize="18sp"
android:textStyle="bold" />
@ -521,7 +526,8 @@
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="12dp"
android:contentDescription="verdict" />
android:contentDescription="@null"
android:importantForAccessibility="no" />
<TextView
android:id="@+id/textVerdict"
@ -577,5 +583,5 @@
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</com.notcvnt.rknhardering.TouchAwareScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -2,10 +2,11 @@
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/colorBackground"
android:fitsSystemWindows="true">
android:fitsSystemWindows="true"
tools:ignore="TooManyViews">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
@ -180,7 +181,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:text="1024" />
android:text="@string/settings_port_range_start_default" />
</com.google.android.material.textfield.TextInputLayout>
<TextView
@ -188,6 +189,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingHorizontal="8dp"
tools:ignore="HardcodedText"
android:text=""
android:textColor="?android:attr/textColorSecondary" />
@ -203,7 +205,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
android:text="65535" />
android:text="@string/settings_port_range_end_default" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
@ -667,7 +669,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_warning"
android:contentDescription="navigate"
android:contentDescription="@null"
android:importantForAccessibility="no"
app:tint="?android:attr/textColorSecondary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@ -718,7 +721,8 @@
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_check_circle"
android:contentDescription="external link"
android:contentDescription="@null"
android:importantForAccessibility="no"
app:tint="?android:attr/textColorSecondary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -1,6 +1,5 @@
<resources>
<string name="app_name">RKNHardering</string>
<string name="github_repo_label">GitHub</string>
<string name="github_repo_url">https://github.com/xtclovver/RKNHardering</string>
<string name="run_check_notice">در زمان اجرای بررسی، برنامه به این دامنه‌ها درخواست شبکه ارسال می‌کند:\n• api.beacondb.net\n• api.ipapi.is\n• www.iplocate.io\n• ipv4-internet.yandex.net\n• ipv6-internet.yandex.net\n• 2ip.ru\n• ifconfig.me\n• ipv4.ifconfig.me\n• ipv6.ifconfig.me\n• checkip.amazonaws.com\n• api.ipify.org\n• api-ipv4.ip.sb\n• api-ipv6.ip.sb\n\nاگر در تنظیمات یک DNS resolver سفارشی انتخاب شده باشد، برنامه همچنین به آدرس‌های IP آن یا دامنه DoH متصل می‌شود.\n\nبا لمس دکمه «اجرای بررسی»، موافقت می‌کنید که این درخواست‌های شبکه از دستگاه شما ارسال شوند.\n\nبرنامه لاگ این درخواست‌ها را نگه نمی‌دارد. دسترسی به شبکه را می‌توان از تنظیمات برنامه غیرفعال کرد.</string>
@ -76,8 +75,6 @@
<string name="main_check_running">بررسی در حال انجام است. کارت‌ها هم‌زمان با رسیدن داده‌ها به‌روزرسانی می‌شوند.</string>
<string name="main_check_stopped">بررسی متوقف شد</string>
<string name="main_status_scanning">در حال اسکن...</string>
<string name="main_status_preparing">در حال آماده‌سازی...</string>
<string name="main_status_cancelled">لغو شد</string>
<string name="main_status_stopped">متوقف شد</string>
@ -208,7 +205,6 @@
<string name="checker_ip_comp_status_ipv6_ignored">IPv6 نادیده گرفته شد</string>
<string name="checker_ip_comp_status_ipv4_ipv6">IPv4/IPv6</string>
<string name="checker_ip_comp_status_mismatch">ناسازگار</string>
<string name="checker_ip_comp_status_partial">ناقص</string>
<string name="checker_ip_comp_status_match">یکسان</string>
<string name="checker_ip_comp_single_ip">IP: %1$s</string>
<string name="checker_ip_comp_single_error">خطا: %1$s</string>
@ -217,7 +213,6 @@
<string name="checker_ip_comp_ipv6_ignored_no_ipv4">خطاهای IPv6 نادیده گرفته شدند، اما پاسخ معتبر IPv4 وجود ندارد</string>
<string name="checker_ip_comp_mixed_families">سرویس‌ها آدرس‌هایی از خانواده‌های متفاوت برگرداندند: %1$s</string>
<string name="checker_ip_comp_different_ips">سرویس‌ها IPهای متفاوتی برگرداندند: %1$s</string>
<string name="checker_ip_comp_partial">IP برابر %1$s است، اما %2$d از %3$d سرویس پاسخ ندادند</string>
<string name="checker_ip_comp_all_same">همه سرویس‌های پاسخ‌داده در این گروه IP %1$s را برگرداندند</string>
<string name="checker_ip_comp_ipv6_ignored_suffix">؛ خطاهای IPv6 نادیده گرفته شد: %1$d</string>
@ -280,15 +275,15 @@
<string name="checker_indirect_dumpsys_service_error">dumpsys activity services: %1$s</string>
<string name="checker_bypass_progress_port_scan_phase">اسکن پورت</string>
<string name="checker_bypass_progress_port_scan_detail">در حال جست‌وجوی proxyهای باز روی localhost...</string>
<string name="checker_bypass_progress_port_scan_detail">در حال جست‌وجوی proxyهای باز روی localhost&#8230;</string>
<string name="checker_bypass_progress_popular_ports">پورت‌های رایج</string>
<string name="checker_bypass_progress_full_scan">اسکن کامل</string>
<string name="checker_bypass_progress_port_detail">پورت %1$d (%2$d%%)</string>
<string name="checker_bypass_progress_xray_detail">در حال جست‌وجوی gRPC API روی localhost...</string>
<string name="checker_bypass_progress_underlying_detail">در حال بررسی دسترسی به شبکه غیر VPN...</string>
<string name="checker_bypass_progress_xray_detail">در حال جست‌وجوی gRPC API روی localhost&#8230;</string>
<string name="checker_bypass_progress_underlying_detail">در حال بررسی دسترسی به شبکه غیر VPN&#8230;</string>
<string name="checker_bypass_progress_ip_phase">بررسی IP</string>
<string name="checker_bypass_progress_ip_detail">در حال به‌دست‌آوردن IP مستقیم و IP از طریق proxy...</string>
<string name="checker_bypass_progress_mtproto_detail">در حال بررسی Telegram DC از طریق proxy...</string>
<string name="checker_bypass_progress_ip_detail">در حال به‌دست‌آوردن IP مستقیم و IP از طریق proxy&#8230;</string>
<string name="checker_bypass_progress_mtproto_detail">در حال بررسی Telegram DC از طریق proxy&#8230;</string>
<string name="checker_bypass_ip_unavailable">در دسترس نیست</string>
<string name="checker_bypass_direct_ip">IP مستقیم: %1$s</string>
<string name="checker_bypass_proxy_ip">IP از طریق proxy: %1$s</string>
@ -301,16 +296,21 @@
<string name="checker_bypass_open_proxy_review_suffix"> (نیازمند تأیید دور زدن)</string>
<string name="checker_bypass_no_xray">Xray gRPC API: پیدا نشد</string>
<string name="checker_bypass_xray_api">Xray gRPC API: %1$s</string>
<string name="checker_bypass_extra_outbounds">&#160;&#160;...%1$d outbound دیگر</string>
<plurals name="checker_bypass_extra_outbounds">
<item quantity="one">&#160;&#160;&#8230;%1$d outbound دیگر</item>
<item quantity="other">&#160;&#160;&#8230;%1$d outbound دیگر</item>
</plurals>
<string name="checker_bypass_vpn_not_active">Underlying network: VPN فعال نیست، این بررسی لازم نیست</string>
<string name="checker_bypass_vpn_network_binding">VPN network binding: برنامه هم به مسیر پیش‌فرض غیر VPN و هم به VPN Network دسترسی دارد (VPN IP: %1$s, IP پیش‌فرض: %2$s)</string>
<string name="checker_bypass_vpn_bound_ip">IP از طریق bind صریح به VPN Network: %1$s</string>
<string name="checker_bypass_tun_probe_success">TUN active probe: درخواست از طریق VPN Network، IP %1$s را برگرداند (TUN یک اینترفیس زنده و قابل مسیریابی است)</string>
<string name="checker_bypass_tun_probe_failure">TUN active probe: درخواست از طریق VPN Network در دسترس نیست (اینترفیس مسدود یا filtered است)</string>
<string name="checker_bypass_tun_probe_failure_reason">بررسی فعال TUN: درخواست از طریق VPN Network با خطا شکست خورد (%1$s)</string>
<string name="checker_bypass_default_non_vpn_ip">IP پیش‌فرض غیر VPN: %1$s</string>
<string name="checker_bypass_underlying_unreachable">Underlying network: شبکه غیر VPN در دسترس نیست (full tunnel)</string>
<string name="checker_bypass_underlying_unreachable_reason">شبکه زیربنایی: شبکه غیر VPN در دسترس نیست (%1$s)</string>
<string name="checker_bypass_gateway_leak">VPN gateway leak: برنامه می‌تواند از تونل VPN عبور کند (IP VPN: %1$s، IP واقعی: %2$s)</string>
<string name="checker_bypass_underlying_same_ip">شبکه underlying در دسترس است، اما IP با VPN%1$s یکسان است: split tunnel تأیید نشد</string>
<string name="checker_bypass_compare_incomplete">مقایسه split tunnel ناقص است (IP VPN: %1$s، IP غیر VPN: %2$s): بررسی دستی لازم است</string>
<string name="checker_vpn_known_apps_none">برنامه‌های شناخته‌شده VPN/Proxy و ارائه‌دهندگان VpnService: موردی پیدا نشد</string>
<string name="checker_vpn_installed_app">برنامه نصب‌شده: %1$s (%2$s)</string>

View file

@ -1,8 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="md_primary">#FFC9C5D0</color>
<color name="md_on_primary">#FF312E37</color>
<color name="md_primary_container">#FF47434D</color>
@ -18,13 +15,10 @@
<color name="status_green">#FF89D998</color>
<color name="status_green_container">#FF193823</color>
<color name="status_green_on_container">#FFB6F5C0</color>
<color name="status_red">#FFFFB4AB</color>
<color name="status_red_container">#FF5A1D1B</color>
<color name="status_red_on_container">#FFFFDAD6</color>
<color name="status_amber">#FFF1CC74</color>
<color name="status_amber_container">#FF4B3900</color>
<color name="status_amber_on_container">#FFFFE7A8</color>
<color name="verdict_green">@color/status_green</color>
<color name="verdict_green_bg">@color/status_green_container</color>

View file

@ -1,6 +1,5 @@
<resources>
<string name="app_name">RKNHardering</string>
<string name="github_repo_label">GitHub</string>
<string name="github_repo_url">https://github.com/xtclovver/RKNHardering</string>
<string name="run_check_notice">Во время проверки приложение выполнит сетевые запросы к доменам:\n• api.beacondb.net\n• api.ipapi.is\n• www.iplocate.io\n• ipv4-internet.yandex.net\n• ipv6-internet.yandex.net\n• 2ip.ru\n• ifconfig.me\n• ipv4.ifconfig.me\n• ipv6.ifconfig.me\n• checkip.amazonaws.com\n• api.ipify.org\n• api-ipv4.ip.sb\n• api-ipv6.ip.sb\n\nЕсли в настройках выбран кастомный DNS-резолвер, приложение также будет обращаться к его IP-адресам или DoH-домену.\n\nНажимая кнопку «Запустить проверку», вы соглашаетесь, что с вашего устройства будут выполнены эти сетевые запросы.\n\nПриложение не ведет логи этих запросов. Доступ к сети можно будет отключить в настройках приложения.</string>
@ -76,8 +75,6 @@
<string name="main_check_running">Проверка идет. Карточки обновляются прямо по мере получения данных.</string>
<string name="main_check_stopped">Проверка остановлена</string>
<string name="main_status_scanning">Сканирование...</string>
<string name="main_status_preparing">Подготовка...</string>
<string name="main_status_cancelled">Отменено</string>
<string name="main_status_stopped">Остановлено</string>
@ -219,7 +216,6 @@
<string name="checker_ip_comp_status_ipv6_ignored">IPv6 игнор</string>
<string name="checker_ip_comp_status_ipv4_ipv6">IPv4/IPv6</string>
<string name="checker_ip_comp_status_mismatch">Разнобой</string>
<string name="checker_ip_comp_status_partial">Частично</string>
<string name="checker_ip_comp_status_match">Совпадает</string>
<string name="checker_ip_comp_single_ip">IP: %1$s</string>
<string name="checker_ip_comp_single_error">Ошибка: %1$s</string>
@ -228,7 +224,6 @@
<string name="checker_ip_comp_ipv6_ignored_no_ipv4">IPv6-ошибки проигнорированы, валидного IPv4-ответа нет</string>
<string name="checker_ip_comp_mixed_families">Сервисы вернули адреса разных семейств: %1$s</string>
<string name="checker_ip_comp_different_ips">Сервисы вернули разные IP: %1$s</string>
<string name="checker_ip_comp_partial">IP %1$s, но %2$d из %3$d сервисов не ответили</string>
<string name="checker_ip_comp_all_same">Все ответившие сервисы группы вернули IP %1$s</string>
<string name="checker_ip_comp_ipv6_ignored_suffix">; IPv6-ошибки проигнорированы: %1$d</string>
@ -294,15 +289,15 @@
<!-- BypassChecker -->
<string name="checker_bypass_progress_port_scan_phase">Сканирование портов</string>
<string name="checker_bypass_progress_port_scan_detail">Поиск открытых прокси на localhost...</string>
<string name="checker_bypass_progress_port_scan_detail">Поиск открытых прокси на localhost&#8230;</string>
<string name="checker_bypass_progress_popular_ports">Популярные порты</string>
<string name="checker_bypass_progress_full_scan">Полное сканирование</string>
<string name="checker_bypass_progress_port_detail">Порт %1$d (%2$d%%)</string>
<string name="checker_bypass_progress_xray_detail">Поиск gRPC API на localhost...</string>
<string name="checker_bypass_progress_underlying_detail">Проверка доступа к non-VPN сети...</string>
<string name="checker_bypass_progress_xray_detail">Поиск gRPC API на localhost&#8230;</string>
<string name="checker_bypass_progress_underlying_detail">Проверка доступа к non-VPN сети&#8230;</string>
<string name="checker_bypass_progress_ip_phase">Проверка IP</string>
<string name="checker_bypass_progress_ip_detail">Получение прямого IP и IP через прокси...</string>
<string name="checker_bypass_progress_mtproto_detail">Проверка Telegram DC через прокси...</string>
<string name="checker_bypass_progress_ip_detail">Получение прямого IP и IP через прокси&#8230;</string>
<string name="checker_bypass_progress_mtproto_detail">Проверка Telegram DC через прокси&#8230;</string>
<string name="checker_bypass_ip_unavailable">не удалось получить</string>
<string name="checker_bypass_direct_ip">Прямой IP: %1$s</string>
<string name="checker_bypass_proxy_ip">IP через прокси: %1$s</string>
@ -315,10 +310,14 @@
<string name="checker_bypass_open_proxy_review_suffix"> (требует подтверждения обхода)</string>
<string name="checker_bypass_no_xray">Xray gRPC API: не обнаружен</string>
<string name="checker_bypass_xray_api">Xray gRPC API: %1$s</string>
<string name="checker_bypass_extra_outbounds">&#160;&#160;...ещё %1$d аутбаундов</string>
<plurals name="checker_bypass_extra_outbounds">
<item quantity="one">&#160;&#160;&#8230;ещё %1$d аутбаунд</item>
<item quantity="few">&#160;&#160;&#8230;ещё %1$d аутбаунда</item>
<item quantity="many">&#160;&#160;&#8230;ещё %1$d аутбаундов</item>
<item quantity="other">&#160;&#160;&#8230;ещё %1$d аутбаунда</item>
</plurals>
<string name="checker_bypass_vpn_not_active">Underlying network: VPN не активен, проверка не требуется</string>
<string name="checker_bypass_vpn_network_binding">VPN network binding: приложение видит и VPN Network, и non-VPN default сеть (VPN IP: %1$s, direct IP: %2$s)</string>
<string name="checker_bypass_vpn_bound_ip">IP через явную привязку к VPN Network: %1$s</string>
<string name="checker_bypass_tun_probe_success">TUN активный зонд: запрос через VPN Network вернул IP %1$s (TUN — живой маршрутизируемый интерфейс)</string>
<string name="checker_bypass_tun_probe_failure">TUN активный зонд: запрос через VPN Network недоступен (интерфейс заблокирован или filtered)</string>
<string name="checker_bypass_tun_probe_failure_reason">TUN активный зонд: запрос через VPN Network завершился ошибкой (%1$s)</string>

View file

@ -1,6 +1,5 @@
<resources>
<string name="app_name">RKNHardering</string>
<string name="github_repo_label">GitHub</string>
<string name="github_repo_url">https://github.com/xtclovver/RKNHardering</string>
<string name="run_check_notice">执行检查时,应用会向以下域名发起网络请求:\n• api.beacondb.net\n• api.ipapi.is\n• www.iplocate.io\n• ipv4-internet.yandex.net\n• ipv6-internet.yandex.net\n• 2ip.ru\n• ifconfig.me\n• ipv4.ifconfig.me\n• ipv6.ifconfig.me\n• checkip.amazonaws.com\n• api.ipify.org\n• api-ipv4.ip.sb\n• api-ipv6.ip.sb\n\n如果在设置中选择了自定义 DNS resolver应用还会连接到其 IP 地址或 DoH 域名。\n\n点击“运行检查”即表示你同意这些网络请求将从你的设备发出。\n\n应用不会记录这些请求的日志。可以在应用设置中关闭网络访问。</string>
@ -76,8 +75,6 @@
<string name="main_check_running">检查正在进行中。卡片会随着数据到达实时更新。</string>
<string name="main_check_stopped">检查已停止</string>
<string name="main_status_scanning">正在扫描...</string>
<string name="main_status_preparing">正在准备...</string>
<string name="main_status_cancelled">已取消</string>
<string name="main_status_stopped">已停止</string>
@ -207,7 +204,6 @@
<string name="checker_ip_comp_status_ipv6_ignored">已忽略 IPv6</string>
<string name="checker_ip_comp_status_ipv4_ipv6">IPv4/IPv6</string>
<string name="checker_ip_comp_status_mismatch">不一致</string>
<string name="checker_ip_comp_status_partial">部分</string>
<string name="checker_ip_comp_status_match">一致</string>
<string name="checker_ip_comp_single_ip">IP%1$s</string>
<string name="checker_ip_comp_single_error">错误:%1$s</string>
@ -216,7 +212,6 @@
<string name="checker_ip_comp_ipv6_ignored_no_ipv4">已忽略 IPv6 错误,但没有有效的 IPv4 响应</string>
<string name="checker_ip_comp_mixed_families">服务返回了不同地址族的地址:%1$s</string>
<string name="checker_ip_comp_different_ips">服务返回了不同的 IP%1$s</string>
<string name="checker_ip_comp_partial">IP 为 %1$s但 %2$d / %3$d 个服务未响应</string>
<string name="checker_ip_comp_all_same">该组所有有响应的服务都返回了 IP %1$s</string>
<string name="checker_ip_comp_ipv6_ignored_suffix">;已忽略 IPv6 错误:%1$d</string>
@ -279,15 +274,15 @@
<string name="checker_indirect_dumpsys_service_error">dumpsys activity services%1$s</string>
<string name="checker_bypass_progress_port_scan_phase">端口扫描</string>
<string name="checker_bypass_progress_port_scan_detail">正在查找 localhost 上的开放代理...</string>
<string name="checker_bypass_progress_port_scan_detail">正在查找 localhost 上的开放代理&#8230;</string>
<string name="checker_bypass_progress_popular_ports">常用端口</string>
<string name="checker_bypass_progress_full_scan">完整扫描</string>
<string name="checker_bypass_progress_port_detail">端口 %1$d%2$d%%</string>
<string name="checker_bypass_progress_xray_detail">正在查找 localhost 上的 gRPC API...</string>
<string name="checker_bypass_progress_underlying_detail">正在检查非 VPN 网络访问能力...</string>
<string name="checker_bypass_progress_xray_detail">正在查找 localhost 上的 gRPC API&#8230;</string>
<string name="checker_bypass_progress_underlying_detail">正在检查非 VPN 网络访问能力&#8230;</string>
<string name="checker_bypass_progress_ip_phase">IP 检查</string>
<string name="checker_bypass_progress_ip_detail">正在获取直接 IP 和经代理的 IP...</string>
<string name="checker_bypass_progress_mtproto_detail">正在通过代理检查 Telegram DC...</string>
<string name="checker_bypass_progress_ip_detail">正在获取直接 IP 和经代理的 IP&#8230;</string>
<string name="checker_bypass_progress_mtproto_detail">正在通过代理检查 Telegram DC&#8230;</string>
<string name="checker_bypass_ip_unavailable">不可用</string>
<string name="checker_bypass_direct_ip">直接 IP%1$s</string>
<string name="checker_bypass_proxy_ip">经代理的 IP%1$s</string>
@ -300,16 +295,20 @@
<string name="checker_bypass_open_proxy_review_suffix">(需要确认是否存在绕过)</string>
<string name="checker_bypass_no_xray">Xray gRPC API未发现</string>
<string name="checker_bypass_xray_api">Xray gRPC API%1$s</string>
<string name="checker_bypass_extra_outbounds">&#160;&#160;...还有 %1$d 个 outbound</string>
<plurals name="checker_bypass_extra_outbounds">
<item quantity="other">&#160;&#160;&#8230;还有 %1$d 个 outbound</item>
</plurals>
<string name="checker_bypass_vpn_not_active">Underlying networkVPN 未激活,无需执行此检查</string>
<string name="checker_bypass_vpn_network_binding">VPN network binding应用同时访问到了默认非 VPN 路径和 VPN NetworkVPN IP%1$s默认 IP%2$s</string>
<string name="checker_bypass_vpn_bound_ip">通过显式绑定到 VPN Network 获得的 IP%1$s</string>
<string name="checker_bypass_tun_probe_success">TUN active probe通过 VPN Network 发起的请求返回了 IP %1$sTUN 是一个可路由的活动接口)</string>
<string name="checker_bypass_tun_probe_failure">TUN active probe通过 VPN Network 发起的请求不可用(接口被阻止或被 filtered</string>
<string name="checker_bypass_tun_probe_failure_reason">TUN 活跃探测:通过 VPN Network 发起的请求失败(%1$s</string>
<string name="checker_bypass_default_non_vpn_ip">默认非 VPN IP%1$s</string>
<string name="checker_bypass_underlying_unreachable">Underlying network非 VPN 网络不可用full tunnel</string>
<string name="checker_bypass_underlying_unreachable_reason">底层网络:非 VPN 网络不可用(%1$s</string>
<string name="checker_bypass_gateway_leak">VPN gateway leak应用可以绕过 VPN 隧道VPN IP%1$s真实 IP%2$s</string>
<string name="checker_bypass_underlying_same_ip">Underlying 网络可达,但 IP 与 VPN%1$s 相同split tunnel 未确认</string>
<string name="checker_bypass_compare_incomplete">Split tunnel 对比不完整VPN IP%1$s非 VPN IP%2$s需要人工复核</string>
<string name="checker_vpn_known_apps_none">已知 VPN/代理应用和 VpnService 提供方:未发现</string>
<string name="checker_vpn_installed_app">已安装应用:%1$s%2$s</string>

View file

@ -1,7 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- M3 Grey Primary palette -->
<color name="md_primary">#FF5D5D6B</color>
@ -21,13 +19,10 @@
<!-- Status colours — used in findings and verdicts -->
<color name="status_green">#FF1A6B2A</color>
<color name="status_green_container">#FFD4EDDA</color>
<color name="status_green_on_container">#FF0F4018</color>
<color name="status_red">#FFB3261E</color>
<color name="status_red_container">#FFFFDAD6</color>
<color name="status_red_on_container">#FF680003</color>
<color name="status_amber">#FF7A5C00</color>
<color name="status_amber_container">#FFFFEFC8</color>
<color name="status_amber_on_container">#FF4A3800</color>
<!-- Legacy aliases — keep for existing code that references these -->
<color name="verdict_green">@color/status_green</color>

View file

@ -1,6 +1,5 @@
<resources>
<string name="app_name">RKNHardering</string>
<string name="github_repo_label">GitHub</string>
<string name="github_repo_url">https://github.com/xtclovver/RKNHardering</string>
<string name="run_check_notice">During a check, the app will make network requests to these domains:\n• api.beacondb.net\n• api.ipapi.is\n• www.iplocate.io\n• ipv4-internet.yandex.net\n• ipv6-internet.yandex.net\n• 2ip.ru\n• ifconfig.me\n• ipv4.ifconfig.me\n• ipv6.ifconfig.me\n• checkip.amazonaws.com\n• api.ipify.org\n• api-ipv4.ip.sb\n• api-ipv6.ip.sb\n\nIf a custom DNS resolver is selected in settings, the app will also connect to its IP addresses or DoH domain.\n\nBy tapping "Run check", you agree that these network requests will be made from your device.\n\nThe app does not keep logs of these requests. Network access can be disabled in the app settings.</string>
@ -59,6 +58,7 @@
<string name="main_subtitle">VPN/Proxy self-check</string>
<string name="main_run_check">Run check</string>
<string name="main_stop_check">Stop</string>
<string name="main_card_geo_ip" translatable="false">GeoIP</string>
<string name="main_card_ip_comparison">IP comparison</string>
<string name="main_card_direct_signs">Direct signs</string>
<string name="main_card_indirect_signs">Indirect signs</string>
@ -76,13 +76,12 @@
<string name="main_check_running">Check in progress. Cards update live as data arrives.</string>
<string name="main_check_stopped">Check stopped</string>
<string name="main_status_scanning">Scanning...</string>
<string name="main_status_preparing">Preparing...</string>
<string name="main_status_cancelled">Cancelled</string>
<string name="main_status_stopped">Stopped</string>
<string name="main_loading_status_scanning">Scanning</string>
<string name="main_loading_status_checking">Checking</string>
<string name="main_loading_status_progress" translatable="false">%1$s%2$s</string>
<string name="main_loading_geo_ip">Checking country, ASN, and hosting indicators.</string>
<string name="main_loading_ip_comparison">Comparing the public IP through multiple independent services.</string>
@ -112,6 +111,8 @@
<string name="main_ipv6_error_ignored">IPv6 error ignored: </string>
<string name="settings_port_range_preview">Localhost proxy ports to scan: %1$s (%2$s)</string>
<string name="settings_port_range_start_default" translatable="false">1024</string>
<string name="settings_port_range_end_default" translatable="false">65535</string>
<plurals name="settings_port_word">
<item quantity="one">%1$s port</item>
<item quantity="other">%1$s ports</item>
@ -217,7 +218,6 @@
<string name="checker_ip_comp_status_ipv6_ignored">IPv6 ignored</string>
<string name="checker_ip_comp_status_ipv4_ipv6">IPv4/IPv6</string>
<string name="checker_ip_comp_status_mismatch">Mismatch</string>
<string name="checker_ip_comp_status_partial">Partial</string>
<string name="checker_ip_comp_status_match">Match</string>
<string name="checker_ip_comp_single_ip">IP: %1$s</string>
<string name="checker_ip_comp_single_error">Error: %1$s</string>
@ -226,7 +226,6 @@
<string name="checker_ip_comp_ipv6_ignored_no_ipv4">IPv6 errors were ignored, but there is no valid IPv4 response</string>
<string name="checker_ip_comp_mixed_families">Services returned addresses from different families: %1$s</string>
<string name="checker_ip_comp_different_ips">Services returned different IPs: %1$s</string>
<string name="checker_ip_comp_partial">IP %1$s, but %2$d of %3$d services did not respond</string>
<string name="checker_ip_comp_all_same">All responding services in the group returned IP %1$s</string>
<string name="checker_ip_comp_ipv6_ignored_suffix">; IPv6 errors ignored: %1$d</string>
@ -292,15 +291,15 @@
<!-- BypassChecker -->
<string name="checker_bypass_progress_port_scan_phase">Port scan</string>
<string name="checker_bypass_progress_port_scan_detail">Looking for open proxies on localhost...</string>
<string name="checker_bypass_progress_port_scan_detail">Looking for open proxies on localhost&#8230;</string>
<string name="checker_bypass_progress_popular_ports">Popular ports</string>
<string name="checker_bypass_progress_full_scan">Full scan</string>
<string name="checker_bypass_progress_port_detail">Port %1$d (%2$d%%)</string>
<string name="checker_bypass_progress_xray_detail">Looking for a gRPC API on localhost...</string>
<string name="checker_bypass_progress_underlying_detail">Checking access to the non-VPN network...</string>
<string name="checker_bypass_progress_xray_detail">Looking for a gRPC API on localhost&#8230;</string>
<string name="checker_bypass_progress_underlying_detail">Checking access to the non-VPN network&#8230;</string>
<string name="checker_bypass_progress_ip_phase">IP check</string>
<string name="checker_bypass_progress_ip_detail">Obtaining the direct IP and the IP via proxy...</string>
<string name="checker_bypass_progress_mtproto_detail">Checking Telegram DC via proxy...</string>
<string name="checker_bypass_progress_ip_detail">Obtaining the direct IP and the IP via proxy&#8230;</string>
<string name="checker_bypass_progress_mtproto_detail">Checking Telegram DC via proxy&#8230;</string>
<string name="checker_bypass_ip_unavailable">unavailable</string>
<string name="checker_bypass_direct_ip">Direct IP: %1$s</string>
<string name="checker_bypass_proxy_ip">IP via proxy: %1$s</string>
@ -313,10 +312,12 @@
<string name="checker_bypass_open_proxy_review_suffix"> (requires bypass confirmation)</string>
<string name="checker_bypass_no_xray">Xray gRPC API: not found</string>
<string name="checker_bypass_xray_api">Xray gRPC API: %1$s</string>
<string name="checker_bypass_extra_outbounds">&#160;&#160;...%1$d more outbounds</string>
<plurals name="checker_bypass_extra_outbounds">
<item quantity="one">&#160;&#160;&#8230;%1$d more outbound</item>
<item quantity="other">&#160;&#160;&#8230;%1$d more outbounds</item>
</plurals>
<string name="checker_bypass_vpn_not_active">Underlying network: VPN is not active, the check is not required</string>
<string name="checker_bypass_vpn_network_binding">VPN network binding: the app can reach both the default non-VPN path and VPN Network (VPN IP: %1$s, default IP: %2$s)</string>
<string name="checker_bypass_vpn_bound_ip">IP via explicit binding to VPN Network: %1$s</string>
<string name="checker_bypass_tun_probe_success">TUN active probe: a request via VPN Network returned IP %1$s (TUN is a live routable interface)</string>
<string name="checker_bypass_tun_probe_failure">TUN active probe: a request via VPN Network is unavailable (the interface is blocked or filtered)</string>
<string name="checker_bypass_tun_probe_failure_reason">TUN active probe: a request via VPN Network failed (%1$s)</string>

View file

@ -14,6 +14,8 @@ grpc = "1.64.0"
protobuf = "3.25.5"
protobufPlugin = "0.9.6"
okhttp = "4.12.0"
robolectric = "4.14.1"
androidxTestCore = "1.6.1"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -27,6 +29,7 @@ androidx-constraintlayout = { group = "androidx.constraintlayout", name = "const
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycle" }
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" }
grpc-okhttp = { group = "io.grpc", name = "grpc-okhttp", version.ref = "grpc" }
grpc-protobuf-lite = { group = "io.grpc", name = "grpc-protobuf-lite", version.ref = "grpc" }
grpc-stub = { group = "io.grpc", name = "grpc-stub", version.ref = "grpc" }
@ -35,6 +38,7 @@ javax-annotation-api = { group = "javax.annotation", name = "javax.annotation-ap
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-dnsoverhttps = { group = "com.squareup.okhttp3", name = "okhttp-dnsoverhttps", version.ref = "okhttp" }
okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

6
lint.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<lint>
<!-- These checks are time-dependent ecosystem advisories and create noisy CI drift. -->
<issue id="GradleDependency" severity="ignore" />
<issue id="NewerVersionAvailable" severity="ignore" />
</lint>