diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt b/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt
index 1c2f8f0..681e1f4 100644
--- a/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/bg/BoxService.kt
@@ -19,6 +19,7 @@ import io.nekohasekai.libbox.CommandServerHandler
import io.nekohasekai.libbox.Libbox
import io.nekohasekai.libbox.PProfServer
import io.nekohasekai.libbox.PlatformInterface
+import io.nekohasekai.libbox.SystemProxyStatus
import io.nekohasekai.sfa.Application
import io.nekohasekai.sfa.constant.Action
import io.nekohasekai.sfa.constant.Alert
@@ -164,6 +165,7 @@ class BoxService(
}
override fun serviceReload() {
+ status.postValue(Status.Starting)
GlobalScope.launch(Dispatchers.IO) {
val pfd = fileDescriptor
if (pfd != null) {
@@ -184,6 +186,19 @@ class BoxService(
}
}
+ override fun getSystemProxyStatus(): SystemProxyStatus {
+ val status = SystemProxyStatus()
+ if (service is VPNService) {
+ status.available = service.systemProxyAvailable
+ status.enabled = service.systemProxyEnabled
+ }
+ return status
+ }
+
+ override fun setSystemProxyEnabled(isEnabled: Boolean) {
+ serviceReload()
+ }
+
@RequiresApi(Build.VERSION_CODES.M)
private fun serviceUpdateIdleMode() {
if (Application.powerManager.isDeviceIdleMode) {
diff --git a/app/src/main/java/io/nekohasekai/sfa/bg/VPNService.kt b/app/src/main/java/io/nekohasekai/sfa/bg/VPNService.kt
index 5be1522..eeed596 100644
--- a/app/src/main/java/io/nekohasekai/sfa/bg/VPNService.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/bg/VPNService.kt
@@ -32,6 +32,9 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
protect(fd)
}
+ var systemProxyAvailable = false
+ var systemProxyEnabled = false
+
override fun openTun(options: TunOptions): Int {
if (prepare(this) != null) error("android: missing vpn permission")
@@ -124,8 +127,10 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
}
if (options.isHTTPProxyEnabled) {
+ systemProxyAvailable = true
+ systemProxyEnabled = Settings.systemProxyEnabled
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- builder.setHttpProxy(
+ if (systemProxyEnabled) builder.setHttpProxy(
ProxyInfo.buildDirectProxy(
options.httpProxyServer,
options.httpProxyServerPort
@@ -134,6 +139,9 @@ class VPNService : VpnService(), PlatformInterfaceWrapper {
} else {
error("android: tun.platform.http_proxy requires android 10 or higher")
}
+ } else {
+ systemProxyAvailable = false
+ systemProxyEnabled = false
}
val pfd =
diff --git a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
index f843e66..40e32d9 100644
--- a/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/constant/SettingsKey.kt
@@ -13,6 +13,8 @@ object SettingsKey {
const val PER_APP_PROXY_LIST = "per_app_proxy_list"
const val PER_APP_PROXY_UPDATE_ON_CHANGE = "per_app_proxy_update_on_change"
+ const val SYSTEM_PROXY_ENABLED = "system_proxy_enabled"
+
// cache
const val STARTED_BY_USER = "started_by_user"
diff --git a/app/src/main/java/io/nekohasekai/sfa/database/ProfileManager.kt b/app/src/main/java/io/nekohasekai/sfa/database/ProfileManager.kt
index c068906..ef33421 100644
--- a/app/src/main/java/io/nekohasekai/sfa/database/ProfileManager.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/database/ProfileManager.kt
@@ -21,9 +21,15 @@ object ProfileManager {
private val instance by lazy {
Application.application.getDatabasePath(Path.PROFILES_DATABASE_PATH).parentFile?.mkdirs()
- Room.databaseBuilder(
- Application.application, ProfileDatabase::class.java, Path.PROFILES_DATABASE_PATH
- ).fallbackToDestructiveMigration().setQueryExecutor { GlobalScope.launch { it.run() } }
+ Room
+ .databaseBuilder(
+ Application.application,
+ ProfileDatabase::class.java,
+ Path.PROFILES_DATABASE_PATH
+ )
+ .fallbackToDestructiveMigration()
+ .enableMultiInstanceInvalidation()
+ .setQueryExecutor { GlobalScope.launch { it.run() } }
.build()
}
diff --git a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
index 3c9fd90..2f7b1a0 100644
--- a/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/database/Settings.kt
@@ -29,6 +29,7 @@ object Settings {
Path.SETTINGS_DATABASE_PATH
).allowMainThreadQueries()
.fallbackToDestructiveMigration()
+ .enableMultiInstanceInvalidation()
.setQueryExecutor { GlobalScope.launch { it.run() } }
.build()
}
@@ -55,6 +56,8 @@ object Settings {
var perAppProxyList by dataStore.stringSet(SettingsKey.PER_APP_PROXY_LIST) { emptySet() }
var perAppProxyUpdateOnChange by dataStore.int(SettingsKey.PER_APP_PROXY_UPDATE_ON_CHANGE) { PER_APP_PROXY_DISABLED }
+ var systemProxyEnabled by dataStore.boolean(SettingsKey.SYSTEM_PROXY_ENABLED) { true }
+
fun serviceClass(): Class<*> {
return when (serviceMode) {
ServiceMode.VPN -> VPNService::class.java
diff --git a/app/src/main/java/io/nekohasekai/sfa/ui/dashboard/OverviewFragment.kt b/app/src/main/java/io/nekohasekai/sfa/ui/dashboard/OverviewFragment.kt
index dd0bc5f..c3aa20c 100644
--- a/app/src/main/java/io/nekohasekai/sfa/ui/dashboard/OverviewFragment.kt
+++ b/app/src/main/java/io/nekohasekai/sfa/ui/dashboard/OverviewFragment.kt
@@ -65,12 +65,14 @@ class OverviewFragment : Fragment() {
binding.profileList.addItemDecoration(divider)
activity.serviceStatus.observe(viewLifecycleOwner) {
binding.statusContainer.isVisible = it == Status.Starting || it == Status.Started
- if (it != Status.Started) {
+ if (it == Status.Stopped) {
binding.clashModeCard.isVisible = false
+ binding.systemProxyCard.isVisible = false
}
if (it == Status.Started) {
statusClient.connect()
clashModeClient.connect()
+ reloadSystemProxyStatus()
}
}
ProfileManager.registerCallback(this::updateProfiles)
@@ -89,6 +91,29 @@ class OverviewFragment : Fragment() {
adapter?.reload()
}
+ private fun reloadSystemProxyStatus() {
+ lifecycleScope.launch(Dispatchers.IO) {
+ val status = Libbox.newStandaloneCommandClient().systemProxyStatus
+ withContext(Dispatchers.Main) {
+ binding.systemProxyCard.isVisible = status.available
+ binding.systemProxyCard.isEnabled = true
+ binding.systemProxySwitch.setOnClickListener(null)
+ binding.systemProxySwitch.isChecked = status.enabled
+ binding.systemProxySwitch.setOnCheckedChangeListener { buttonView, isChecked ->
+ binding.systemProxyCard.isEnabled = false
+ lifecycleScope.launch(Dispatchers.IO) {
+ Settings.systemProxyEnabled = isChecked
+ runCatching {
+ Libbox.newStandaloneCommandClient().setSystemProxyEnabled(isChecked)
+ }.onFailure {
+ buttonView.context.errorDialogBuilder(it).show()
+ }
+ }
+ }
+ }
+ }
+ }
+
inner class StatusClient : CommandClient.Handler {
override fun onConnected() {
diff --git a/app/src/main/res/layout/fragment_dashboard_overview.xml b/app/src/main/res/layout/fragment_dashboard_overview.xml
index 127091f..0ca87fe 100644
--- a/app/src/main/res/layout/fragment_dashboard_overview.xml
+++ b/app/src/main/res/layout/fragment_dashboard_overview.xml
@@ -429,18 +429,18 @@
+
+
+
+
+
+
+
+
+
+
Search
URLTest
Expand
+ HTTP Proxy
\ No newline at end of file