12 Commits

24 changed files with 224 additions and 232 deletions
+1 -1
View File
@@ -167,5 +167,5 @@ jobs:
with:
files: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*.apk
tag_name: ${{ github.event.inputs.release_tag || github.ref_name }}
prerelease: true
prerelease: false
generate_release_notes: true
+11
View File
@@ -28,6 +28,7 @@
android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@@ -210,6 +211,16 @@
</intent-filter>
</receiver>
<receiver
android:name=".receiver.ServiceControlReceiver"
android:exported="false"
android:process=":RunSoLibV2RayDaemon">
<intent-filter>
<action android:name="${applicationId}.action.service.stop" />
<action android:name="${applicationId}.action.service.start" />
</intent-filter>
</receiver>
<service
android:name=".service.QSTileService"
android:exported="true"
@@ -72,7 +72,6 @@ object AppConfig {
const val PREF_USE_HEV_TUNNEL = "pref_use_hev_tunnel_v2"
const val PREF_HEV_TUNNEL_LOGLEVEL = "pref_hev_tunnel_loglevel"
const val PREF_HEV_TUNNEL_RW_TIMEOUT = "pref_hev_tunnel_rw_timeout_v2"
const val PREF_AUTO_REMOVE_INVALID_AFTER_TEST = "pref_auto_remove_invalid_after_test"
const val PREF_AUTO_SORT_AFTER_TEST = "pref_auto_sort_after_test"
/** Cache keys. */
@@ -85,6 +84,8 @@ object AppConfig {
const val BROADCAST_ACTION_SERVICE = "$ANG_PACKAGE.action.service"
const val BROADCAST_ACTION_ACTIVITY = "$ANG_PACKAGE.action.activity"
const val BROADCAST_ACTION_WIDGET_CLICK = "$ANG_PACKAGE.action.widget.click"
const val BROADCAST_ACTION_SERVICE_STOP = "$ANG_PACKAGE.action.service.stop"
const val BROADCAST_ACTION_SERVICE_START = "$ANG_PACKAGE.action.service.start"
/** Tasker extras. */
const val TASKER_EXTRA_BUNDLE = "com.twofortyfouram.locale.intent.extra.BUNDLE"
@@ -2,9 +2,12 @@ package xyz.zarazaex.olc.dto
data class ServerAffiliationInfo(var testDelayMillis: Long = 0L) {
fun getTestDelayString(): String {
if (testDelayMillis == 0L) {
return ""
return when {
testDelayMillis == 0L -> ""
testDelayMillis < 0L -> "Error"
else -> "${testDelayMillis}ms"
}
return testDelayMillis.toString() + "ms"
}
fun isReachable(): Boolean = testDelayMillis > 0L
}
@@ -292,34 +292,6 @@ object MmkvManager {
return count
}
/**
* Removes invalid server configurations.
*
* @param guid The server GUID.
* @return The number of server configurations removed.
*/
fun removeInvalidServer(guid: String): Int {
var count = 0
if (guid.isNotEmpty()) {
decodeServerAffiliationInfo(guid)?.let { aff ->
if (aff.testDelayMillis < 0L) {
removeServer(guid)
count++
}
}
} else {
serverAffStorage.allKeys()?.forEach { key ->
decodeServerAffiliationInfo(key)?.let { aff ->
if (aff.testDelayMillis < 0L) {
removeServer(key)
count++
}
}
}
}
return count
}
/**
* Encodes the raw server configuration.
*
@@ -17,47 +17,55 @@ import java.io.FileOutputStream
object UpdateCheckerManager {
suspend fun checkForUpdate(includePreRelease: Boolean = false): CheckUpdateResult = withContext(Dispatchers.IO) {
val url = if (includePreRelease) {
AppConfig.APP_API_URL
} else {
AppConfig.APP_API_URL.concatUrl("latest")
}
try {
val url = if (includePreRelease) {
AppConfig.APP_API_URL
} else {
AppConfig.APP_API_URL.concatUrl("latest")
}
var response = HttpUtil.getUrlContent(url, 5000)
if (response.isNullOrEmpty()) {
val httpPort = SettingsManager.getHttpPort()
response = HttpUtil.getUrlContent(url, 5000, httpPort)
?: throw IllegalStateException("Failed to get response")
}
var response = HttpUtil.getUrlContent(url, 5000)
if (response.isNullOrEmpty()) {
val httpPort = SettingsManager.getHttpPort()
response = HttpUtil.getUrlContent(url, 5000, httpPort)
?: throw IllegalStateException("Failed to get response")
}
val latestRelease = if (includePreRelease) {
JsonUtil.fromJson(response, Array<GitHubRelease>::class.java)
?.firstOrNull()
?: throw IllegalStateException("No pre-release found")
} else {
JsonUtil.fromJson(response, GitHubRelease::class.java)
}
if (latestRelease == null) {
return@withContext CheckUpdateResult(hasUpdate = false)
}
val latestRelease = if (includePreRelease) {
JsonUtil.fromJson(response, Array<GitHubRelease>::class.java)
?.firstOrNull()
?: throw IllegalStateException("No pre-release found")
} else {
JsonUtil.fromJson(response, GitHubRelease::class.java)
}
if (latestRelease == null) {
return@withContext CheckUpdateResult(hasUpdate = false)
}
val latestVersion = latestRelease.tagName.removePrefix("v")
Log.i(
AppConfig.TAG,
"Found new version: $latestVersion (current: ${BuildConfig.VERSION_NAME})"
)
return@withContext if (compareVersions(latestVersion, BuildConfig.VERSION_NAME) > 0) {
val downloadUrl = getDownloadUrl(latestRelease, Build.SUPPORTED_ABIS[0])
CheckUpdateResult(
hasUpdate = true,
latestVersion = latestVersion,
releaseNotes = latestRelease.body,
downloadUrl = downloadUrl,
isPreRelease = latestRelease.prerelease
val latestVersion = latestRelease.tagName.removePrefix("v")
Log.i(
AppConfig.TAG,
"Found new version: $latestVersion (current: ${BuildConfig.VERSION_NAME})"
)
} else {
CheckUpdateResult(hasUpdate = false)
return@withContext if (compareVersions(latestVersion, BuildConfig.VERSION_NAME) > 0) {
val downloadUrl = getDownloadUrl(latestRelease, Build.SUPPORTED_ABIS[0])
CheckUpdateResult(
hasUpdate = true,
latestVersion = latestVersion,
releaseNotes = latestRelease.body,
downloadUrl = downloadUrl,
isPreRelease = latestRelease.prerelease
)
} else {
CheckUpdateResult(hasUpdate = false)
}
} catch (e: NumberFormatException) {
Log.e(AppConfig.TAG, "Failed to parse version: ${e.message}")
return@withContext CheckUpdateResult(hasUpdate = false)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Failed to check for update: ${e.message}")
throw e
}
}
@@ -95,12 +103,15 @@ object UpdateCheckerManager {
}
private fun compareVersions(version1: String, version2: String): Int {
val v1 = version1.split(".")
val v2 = version2.split(".")
val cleanVersion1 = version1.split("-")[0]
val cleanVersion2 = version2.split("-")[0]
val v1 = cleanVersion1.split(".")
val v2 = cleanVersion2.split(".")
for (i in 0 until maxOf(v1.size, v2.size)) {
val num1 = if (i < v1.size) v1[i].toInt() else 0
val num2 = if (i < v2.size) v2[i].toInt() else 0
val num1 = if (i < v1.size) v1[i].toIntOrNull() ?: 0 else 0
val num2 = if (i < v2.size) v2[i].toIntOrNull() ?: 0 else 0
if (num1 != num2) return num1 - num2
}
return 0
@@ -32,6 +32,7 @@ object V2RayServiceManager {
private var currentConfig: ProfileItem? = null
private val operationLock = Any()
@Volatile private var isOperationInProgress = false
@Volatile var isIntentionalStop = false
var serviceControl: SoftReference<ServiceControl>? = null
set(value) {
@@ -49,6 +50,7 @@ object V2RayServiceManager {
context.toast(R.string.app_tile_first_use)
return false
}
isIntentionalStop = false
startContextService(context)
return true
}
@@ -74,6 +76,7 @@ object V2RayServiceManager {
MmkvManager.setSelectServer(guid)
}
isIntentionalStop = false
startContextService(context)
} finally {
synchronized(operationLock) {
@@ -87,26 +90,16 @@ object V2RayServiceManager {
* @param context The context from which the service is stopped.
*/
fun stopVService(context: Context) {
synchronized(operationLock) {
if (isOperationInProgress) {
Log.w(AppConfig.TAG, "StartCore-Manager: Operation already in progress")
return
}
isOperationInProgress = true
}
try {
if (serviceControl?.get() == null) {
Log.w(AppConfig.TAG, "StartCore-Manager: Service not running, resetting UI state")
MessageUtil.sendMsg2UI(context, AppConfig.MSG_STATE_STOP_SUCCESS, "")
return
}
MessageUtil.sendMsg2Service(context, AppConfig.MSG_STATE_STOP, "")
} finally {
synchronized(operationLock) {
isOperationInProgress = false
}
Log.i(AppConfig.TAG, "StartCore-Manager: stopVService called")
isIntentionalStop = true
val svc = serviceControl?.get()
if (svc != null) {
svc.stopService()
return
}
val intent = Intent(AppConfig.BROADCAST_ACTION_SERVICE_STOP)
intent.setPackage(AppConfig.ANG_PACKAGE)
context.sendBroadcast(intent)
}
/**
@@ -262,23 +255,26 @@ object V2RayServiceManager {
fun stopCoreLoop(): Boolean {
val service = getService() ?: return false
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to unregister receiver", e)
}
NotificationManager.cancelNotification()
if (coreController.isRunning) {
CoroutineScope(Dispatchers.IO).launch {
try {
coreController.stopLoop()
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to stop V2Ray loop", e)
} finally {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
}
}
}
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
NotificationManager.cancelNotification()
try {
service.unregisterReceiver(mMsgReceive)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to unregister receiver", e)
} else {
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_STOP_SUCCESS, "")
}
return true
@@ -368,10 +364,24 @@ object V2RayServiceManager {
override fun shutdown(): Long {
val serviceControl = serviceControl?.get() ?: return -1
return try {
serviceControl.stopService()
Log.w(AppConfig.TAG, "StartCore-Manager: Core shutdown callback, attempting restart")
val service = serviceControl.getService()
MessageUtil.sendMsg2UI(service, AppConfig.MSG_STATE_NOT_RUNNING, "")
if (isIntentionalStop) {
Log.i(AppConfig.TAG, "StartCore-Manager: Intentional stop, skipping restart")
return 0
}
CoroutineScope(Dispatchers.IO).launch {
kotlinx.coroutines.delay(1000L)
val ctx = service.applicationContext
if (coreController.isRunning == false) {
Log.i(AppConfig.TAG, "StartCore-Manager: Restarting service after core shutdown")
startVService(ctx)
}
}
0
} catch (e: Exception) {
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to stop service", e)
Log.e(AppConfig.TAG, "StartCore-Manager: Failed to handle core shutdown", e)
-1
}
}
@@ -399,22 +409,20 @@ object V2RayServiceManager {
* @param intent The intent being received.
*/
override fun onReceive(ctx: Context?, intent: Intent?) {
val serviceControl = serviceControl?.get() ?: return
when (intent?.getIntExtra("key", 0)) {
AppConfig.MSG_REGISTER_CLIENT -> {
val svc = serviceControl?.get() ?: return
if (coreController.isRunning) {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_RUNNING, "")
MessageUtil.sendMsg2UI(svc.getService(), AppConfig.MSG_STATE_RUNNING, "")
} else {
MessageUtil.sendMsg2UI(serviceControl.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
MessageUtil.sendMsg2UI(svc.getService(), AppConfig.MSG_STATE_NOT_RUNNING, "")
}
}
AppConfig.MSG_UNREGISTER_CLIENT -> {
// nothing to do
}
AppConfig.MSG_STATE_START -> {
// nothing to do
}
AppConfig.MSG_STATE_STOP -> {
@@ -422,7 +430,15 @@ object V2RayServiceManager {
synchronized(operationLock) {
isOperationInProgress = false
}
serviceControl.stopService()
val svc = serviceControl?.get()
if (svc != null) {
svc.stopService()
} else if (ctx != null) {
Log.w(AppConfig.TAG, "StartCore-Manager: serviceControl null on stop, stopping core directly")
stopCoreLoop()
ctx.stopService(Intent(ctx, V2RayVpnService::class.java))
ctx.stopService(Intent(ctx, V2RayProxyOnlyService::class.java))
}
}
AppConfig.MSG_STATE_RESTART -> {
@@ -430,9 +446,9 @@ object V2RayServiceManager {
synchronized(operationLock) {
isOperationInProgress = false
}
serviceControl.stopService()
serviceControl?.get()?.stopService()
Thread.sleep(500L)
startVService(serviceControl.getService())
if (ctx != null) startVService(ctx)
}
AppConfig.MSG_MEASURE_DELAY -> {
@@ -0,0 +1,19 @@
package xyz.zarazaex.olc.receiver
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import xyz.zarazaex.olc.AppConfig
import xyz.zarazaex.olc.handler.V2RayServiceManager
class ServiceControlReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
AppConfig.BROADCAST_ACTION_SERVICE_STOP -> {
V2RayServiceManager.isIntentionalStop = true
V2RayServiceManager.stopVService(context)
}
AppConfig.BROADCAST_ACTION_SERVICE_START -> V2RayServiceManager.startVServiceFromToggle(context)
}
}
}
@@ -77,50 +77,25 @@ class RealPingWorkerService(
}
private suspend fun startRealPing(guid: String): Long {
val retFailure = -1L
val configResult = V2rayConfigManager.getV2rayConfig4Speedtest(context, guid)
if (!configResult.status) {
return retFailure
}
if (!configResult.status) return -1L
var bestDelay = retFailure
for (attempt in 0 until 2) {
val urls = listOf(
SettingsManager.getDelayTestUrl(),
SettingsManager.getDelayTestUrl(true)
)
for (url in urls) {
try {
val delay = withTimeout(10000L) {
V2RayNativeManager.measureOutboundDelay(
configResult.content,
SettingsManager.getDelayTestUrl()
)
}
if (delay > 0 && (bestDelay == retFailure || delay < bestDelay)) {
bestDelay = delay
}
if (bestDelay > 0) {
break
}
} catch (e: Exception) {
if (attempt == 0) {
try {
val delay = withTimeout(10000L) {
V2RayNativeManager.measureOutboundDelay(
configResult.content,
SettingsManager.getDelayTestUrl(true)
)
}
if (delay > 0 && (bestDelay == retFailure || delay < bestDelay)) {
bestDelay = delay
}
} catch (_: Exception) {
}
V2RayNativeManager.measureOutboundDelay(configResult.content, url)
}
if (delay > 0) return delay
} catch (_: Exception) {
}
}
return bestDelay
return -1L
}
}
@@ -32,7 +32,7 @@ class V2RayProxyOnlyService : Service(), ServiceControl {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(AppConfig.TAG, "StartCore-Proxy: Service command received")
V2RayServiceManager.startCoreLoop(null)
return START_STICKY
return START_NOT_STICKY
}
/**
@@ -13,6 +13,7 @@ import android.net.ProxyInfo
import android.net.VpnService
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.PowerManager
import android.os.StrictMode
import android.util.Log
import androidx.annotation.RequiresApi
@@ -35,6 +36,7 @@ class V2RayVpnService : VpnService(), ServiceControl {
private lateinit var mInterface: ParcelFileDescriptor
private var isRunning = false
private var tun2SocksService: Tun2SocksControl? = null
private var wakeLock: PowerManager.WakeLock? = null
/**destroy
* Unfortunately registerDefaultNetworkCallback is going to return our VPN interface: https://android.googlesource.com/platform/frameworks/base/+/dda156ab0c5d66ad82bdcf76cda07cbc0a9c8a2e
@@ -79,6 +81,9 @@ class V2RayVpnService : VpnService(), ServiceControl {
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy)
V2RayServiceManager.serviceControl = SoftReference(this)
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager)
.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "v2rayng:vpn")
.also { it.acquire() }
}
override fun onRevoke() {
@@ -94,16 +99,16 @@ class V2RayVpnService : VpnService(), ServiceControl {
override fun onDestroy() {
super.onDestroy()
Log.i(AppConfig.TAG, "StartCore-VPN: Service destroyed")
NotificationManager.cancelNotification()
MessageUtil.sendMsg2UI(this, AppConfig.MSG_STATE_STOP_SUCCESS, "")
stopAllService(false)
wakeLock?.let { if (it.isHeld) it.release() }
wakeLock = null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(AppConfig.TAG, "StartCore-VPN: Service command received")
setupVpnService()
startService()
return START_STICKY
//return super.onStartCommand(intent, flags, startId)
return START_NOT_STICKY
}
override fun getService(): Service {
@@ -34,6 +34,7 @@ import xyz.zarazaex.olc.handler.SettingsChangeManager
import xyz.zarazaex.olc.handler.SettingsManager
import xyz.zarazaex.olc.handler.UpdateCheckerManager
import xyz.zarazaex.olc.handler.V2RayServiceManager
import xyz.zarazaex.olc.util.MessageUtil
import xyz.zarazaex.olc.util.Utils
import xyz.zarazaex.olc.viewmodel.MainViewModel
import kotlinx.coroutines.Dispatchers
@@ -147,7 +148,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
setupGroupTab()
setupViewModel()
mainViewModel.reloadServerList()
importConfigViaSub()
importAllSubsOnStartup()
checkAndRequestPermission(PermissionType.POST_NOTIFICATIONS) {
}
@@ -163,14 +164,16 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
isLiteTesting = false
mainViewModel.sortByTestResults()
mainViewModel.reloadServerList()
val firstServer = mainViewModel.serversCache.firstOrNull()
if (firstServer != null) {
MmkvManager.setSelectServer(firstServer.guid)
val firstReachable = mainViewModel.serversCache.firstOrNull { cache ->
(MmkvManager.decodeServerAffiliationInfo(cache.guid)?.testDelayMillis ?: 0L) > 0L
}
if (firstReachable != null) {
MmkvManager.setSelectServer(firstReachable.guid)
showStatus("Подключаемся к быстрейшему серверу")
startV2RayWithPermission()
} else {
showStatus("Серверы не найдены!")
showStatus("Нет доступных серверов!")
}
}
}
@@ -205,17 +208,23 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
isFabOperationInProgress = true
val isRunning = mainViewModel.isRunning.value == true
applyRunningState(isLoading = true, isRunning = false)
lifecycleScope.launch {
try {
if (mainViewModel.isRunning.value == true) {
if (isRunning) {
Log.d(AppConfig.TAG, "FAB: stopping service")
V2RayServiceManager.stopVService(this@MainActivity)
} else {
Log.d(AppConfig.TAG, "FAB: starting service")
startV2RayWithPermission()
}
} catch (e: Exception) {
Log.e(AppConfig.TAG, "FAB: error", e)
applyRunningState(isLoading = false, isRunning = mainViewModel.isRunning.value == true)
} finally {
delay(1000)
isFabOperationInProgress = false
}
}
@@ -264,8 +273,10 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
mainViewModel.testAllRealPing()
}
}
delay(1500)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error in handleLiteAction", e)
} finally {
delay(1000)
isFabOperationInProgress = false
}
}
@@ -302,11 +313,13 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
try {
if (mainViewModel.isRunning.value == true) {
V2RayServiceManager.stopVService(this@MainActivity)
delay(1000)
}
delay(1000)
startV2Ray()
delay(1000)
} catch (e: Exception) {
Log.e(AppConfig.TAG, "Error in restartV2Ray", e)
} finally {
delay(500)
isFabOperationInProgress = false
}
}
@@ -359,6 +372,7 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
override fun onResume() {
super.onResume()
MessageUtil.sendMsg2Service(this, AppConfig.MSG_REGISTER_CLIENT, "")
}
override fun onPause() {
@@ -474,6 +488,20 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
}
private fun importAllSubsOnStartup() {
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
val result = AngConfigManager.updateConfigViaSubAll()
delay(500L)
launch(Dispatchers.Main) {
if (result.configCount > 0) {
mainViewModel.reloadServerList()
showStatus(getString(R.string.title_update_config_count, result.configCount))
}
hideLoading()
}
}
}
/**
* import config from sub
*/
@@ -557,25 +585,6 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
.show()
}
private fun delInvalidConfig() {
AlertDialog.Builder(this).setMessage(R.string.del_invalid_config_comfirm)
.setPositiveButton(android.R.string.ok) { _, _ ->
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
val ret = mainViewModel.removeInvalidServer()
launch(Dispatchers.Main) {
mainViewModel.reloadServerList()
showStatus(getString(R.string.title_del_config_count, ret))
hideLoading()
}
}
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
//do noting
}
.show()
}
private fun sortByTestResults() {
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
@@ -96,7 +96,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val subServers = MmkvManager.decodeServerList(sub.guid)
subServers.forEach { guid ->
val delay = MmkvManager.decodeServerAffiliationInfo(guid)?.testDelayMillis ?: 0L
allServers.add(ServerWithDelay(guid, if (delay <= 0L) 999999 else delay))
val sortKey = when {
delay > 0L -> delay
delay == 0L -> Long.MAX_VALUE - 1
else -> Long.MAX_VALUE
}
allServers.add(ServerWithDelay(guid, sortKey))
}
}
@@ -445,23 +450,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
return count
}
/**
* Removes invalid servers.
* @return The number of removed servers.
*/
fun removeInvalidServer(): Int {
var count = 0
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
count += MmkvManager.removeInvalidServer("")
} else {
val serversCopy = serversCache.toList()
for (item in serversCopy) {
count += MmkvManager.removeInvalidServer(item.guid)
}
}
return count
}
/**
* Sorts servers by their test results.
*/
@@ -499,7 +487,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
val serverList = MmkvManager.decodeServerList(sub.guid)
serverList.forEach { guid ->
val delay = MmkvManager.decodeServerAffiliationInfo(guid)?.testDelayMillis ?: 0L
allServerDelays.add(ServerDelay(guid, if (delay <= 0L) 999999 else delay, sub.guid))
val sortKey = when {
delay > 0L -> delay
delay == 0L -> Long.MAX_VALUE - 1
else -> Long.MAX_VALUE
}
allServerDelays.add(ServerDelay(guid, sortKey, sub.guid))
}
}
@@ -524,7 +517,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
serverListToSort.forEach { key ->
val delay = MmkvManager.decodeServerAffiliationInfo(key)?.testDelayMillis ?: 0L
serverDelays.add(ServerDelay(key, if (delay <= 0L) 999999 else delay))
val sortKey = when {
delay > 0L -> delay
delay == 0L -> Long.MAX_VALUE - 1
else -> Long.MAX_VALUE
}
serverDelays.add(ServerDelay(key, sortKey))
}
serverDelays.sortBy { it.testDelayMillis }
@@ -580,10 +578,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
fun onTestsFinished() {
viewModelScope.launch(Dispatchers.Default) {
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_AUTO_REMOVE_INVALID_AFTER_TEST)) {
removeInvalidServer()
}
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_AUTO_SORT_AFTER_TEST, true)) {
sortByTestResults()
}
@@ -156,8 +156,6 @@
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
@@ -154,8 +154,6 @@
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
@@ -155,8 +155,6 @@
<string name="title_pref_is_booted">منپیز خوتکار مجال ره ونی</string>
<string name="summary_pref_is_booted">مجال ره وندن، خوساخوس و سرور پسند بیڌه منپیز ابۊ که گاشڌ نا مووفق بۊ.</string>
<string name="title_pref_auto_remove_invalid_after_test">پاک کردن خوتکار کانفیگ نا موئتبر بئڌ آزمایش</string>
<string name="summary_pref_auto_remove_invalid_after_test">نتیجه یل آزمایش گاشڌ دییق نبۊن؛ کانفیگ پاک وابیڌه ن نتری وورگنی.</string>
<string name="title_pref_auto_sort_after_test">ترتیب خوتکار بئڌ آزمایش</string>
<string name="summary_pref_auto_sort_after_test">نتیجه یل آزمایش گاشڌ دییق نبۊن؛</string>
@@ -152,8 +152,6 @@
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد.</string>
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
@@ -156,8 +156,6 @@
<string name="title_pref_is_booted">Автоподключение при запуске</string>
<string name="summary_pref_is_booted">Автоматически подключаться к выбранному серверу при запуске приложения (может оказаться неудачным)</string>
<string name="title_pref_auto_remove_invalid_after_test">Автоудаление нерабочих профилей</string>
<string name="summary_pref_auto_remove_invalid_after_test">Автоматическое удаление нерабочих профилей после проверки (результаты проверки могут быть неточными; восстановить удалённые профили невозможно)</string>
<string name="title_pref_auto_sort_after_test">Автосортировка профилей</string>
<string name="summary_pref_auto_sort_after_test">Автоматическая сортировка профилей после проверки (результаты проверки могут быть неточными)</string>
@@ -153,8 +153,6 @@
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
@@ -152,8 +152,6 @@
<string name="title_pref_is_booted">开机时自动连接</string>
<string name="summary_pref_is_booted">开机时自动连接选择的服务器,可能会不成功</string>
<string name="title_pref_auto_remove_invalid_after_test">测试后自动删除无效配置</string>
<string name="summary_pref_auto_remove_invalid_after_test">测试结果可能不准确;已删除的配置无法恢复。</string>
<string name="title_pref_auto_sort_after_test">测试后自动排序</string>
<string name="summary_pref_auto_sort_after_test">测试结果可能不准确;</string>
@@ -153,8 +153,6 @@
<string name="title_pref_is_booted">開機時自動連線</string>
<string name="summary_pref_is_booted">開機時自動連線選擇的伺服器,可能會不成功</string>
<string name="title_pref_auto_remove_invalid_after_test">測試後自動刪除無效配置</string>
<string name="summary_pref_auto_remove_invalid_after_test">測試結果可能不準確;已刪除的配置無法復原。 </string>
<string name="title_pref_auto_sort_after_test">測試後自動排序</string>
<string name="summary_pref_auto_sort_after_test">測試結果可能不準確;</string>
@@ -157,8 +157,6 @@
<string name="title_pref_is_booted">Auto connect at startup</string>
<string name="summary_pref_is_booted">Automatically connects to the selected server at startup, which may be unsuccessful</string>
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
@@ -266,12 +266,6 @@
android:summary="@string/summary_pref_is_booted"
android:title="@string/title_pref_is_booted" />
<CheckBoxPreference
android:key="pref_auto_remove_invalid_after_test"
android:defaultValue="false"
android:summary="@string/summary_pref_auto_remove_invalid_after_test"
android:title="@string/title_pref_auto_remove_invalid_after_test" />
<CheckBoxPreference
android:key="pref_auto_sort_after_test"
android:defaultValue="false"
BIN
View File
Binary file not shown.