Add system proxy toggle

This commit is contained in:
世界 2023-09-03 22:55:19 +08:00
parent 1c8cc6b3cf
commit 03f9dc4883
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
8 changed files with 95 additions and 9 deletions

View file

@ -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) {

View file

@ -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 =

View file

@ -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"

View file

@ -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()
}

View file

@ -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

View file

@ -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() {

View file

@ -429,18 +429,18 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mode"
android:paddingTop="16dp"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:text="Mode"
android:textAppearance="?attr/textAppearanceTitleSmall">
</TextView>
<androidx.recyclerview.widget.RecyclerView
android:nestedScrollingEnabled="false"
android:id="@+id/clashModeList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="false"
android:padding="8dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
@ -452,13 +452,39 @@
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileCard"
android:id="@+id/systemProxyCard"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/systemProxySwitch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/http_proxy"
android:textAppearance="?attr/textAppearanceTitleSmall" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileCard"
style="?attr/materialCardViewElevatedStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -132,4 +132,5 @@
<string name="search">Search</string>
<string name="urltest">URLTest</string>
<string name="expand">Expand</string>
<string name="http_proxy">HTTP Proxy</string>
</resources>