Commit a1834e63 authored by Mygod's avatar Mygod

Merge branch 'preference-1.1' into feature-hosts

parents b89dccd7 50d09e73
......@@ -9,7 +9,6 @@ buildscript {
sdkVersion = 28
compileSdkVersion = 28
buildToolsVersion = '28.0.3'
preferencexVersion = '1.0.0'
junitVersion = '4.12'
androidTestVersion = '1.1.1'
androidEspressoVersion = '3.1.1'
......
......@@ -51,12 +51,11 @@ dependencies {
api "android.arch.work:work-runtime-ktx:$workVersion"
api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
api 'androidx.preference:preference:1.0.0'
api 'androidx.preference:preference:1.1.0-alpha04'
api "androidx.room:room-runtime:$roomVersion"
api 'com.crashlytics.sdk.android:crashlytics:2.9.9'
api 'com.google.firebase:firebase-config:16.1.3'
api 'com.google.firebase:firebase-core:16.0.7'
api "com.takisoft.preferencex:preferencex:$preferencexVersion"
api 'dnsjava:dnsjava:2.1.8'
api 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
api 'org.connectbot.jsocks:jsocks:1.0.0'
......
{
"formatVersion": 1,
"database": {
"version": 28,
"identityHash": "b60ecca4d684ffe73173478bffd50a17",
"entities": [
{
"tableName": "Profile",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT, `host` TEXT NOT NULL, `remotePort` INTEGER NOT NULL, `password` TEXT NOT NULL, `method` TEXT NOT NULL, `route` TEXT NOT NULL, `remoteDns` TEXT NOT NULL, `proxyApps` INTEGER NOT NULL, `bypass` INTEGER NOT NULL, `udpdns` INTEGER NOT NULL, `ipv6` INTEGER NOT NULL, `metered` INTEGER NOT NULL, `individual` TEXT NOT NULL, `tx` INTEGER NOT NULL, `rx` INTEGER NOT NULL, `userOrder` INTEGER NOT NULL, `plugin` TEXT, `udpFallback` INTEGER)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "host",
"columnName": "host",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remotePort",
"columnName": "remotePort",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "method",
"columnName": "method",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "route",
"columnName": "route",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "remoteDns",
"columnName": "remoteDns",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "proxyApps",
"columnName": "proxyApps",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "bypass",
"columnName": "bypass",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "udpdns",
"columnName": "udpdns",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ipv6",
"columnName": "ipv6",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "metered",
"columnName": "metered",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "individual",
"columnName": "individual",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tx",
"columnName": "tx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rx",
"columnName": "rx",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "userOrder",
"columnName": "userOrder",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "plugin",
"columnName": "plugin",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "udpFallback",
"columnName": "udpFallback",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "KeyValuePair",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `valueType` INTEGER NOT NULL, `value` BLOB NOT NULL, PRIMARY KEY(`key`))",
"fields": [
{
"fieldPath": "key",
"columnName": "key",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "valueType",
"columnName": "valueType",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "value",
"columnName": "value",
"affinity": "BLOB",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"key"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"b60ecca4d684ffe73173478bffd50a17\")"
]
}
}
\ No newline at end of file
......@@ -46,4 +46,11 @@ class MigrationTest {
db.close()
privateDatabase.runMigrationsAndValidate(TEST_DB, 27, true, PrivateDatabase.Migration27)
}
@Test
@Throws(IOException::class)
fun migrate28() {
val db = privateDatabase.createDatabase(TEST_DB, 27)
db.close()
privateDatabase.runMigrationsAndValidate(TEST_DB, 28, true, PrivateDatabase.Migration28)
}
}
......@@ -101,12 +101,15 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
private var conn: ParcelFileDescriptor? = null
private var worker: ProtectWorker? = null
private var active = false
private var metered = false
private var underlyingNetwork: Network? = null
set(value) {
field = value
if (active && Build.VERSION.SDK_INT >= 22) setUnderlyingNetworks(underlyingNetworks)
}
private val underlyingNetworks get() = underlyingNetwork?.let { arrayOf(it) }
private val underlyingNetworks get() =
// clearing underlyingNetworks makes Android 9+ consider the network to be metered
if (Build.VERSION.SDK_INT >= 28 && metered) null else underlyingNetwork?.let { arrayOf(it) }
override fun onBind(intent: Intent) = when (intent.action) {
SERVICE_INTERFACE -> super<BaseVpnService>.onBind(intent)
......@@ -190,6 +193,7 @@ class VpnService : BaseVpnService(), LocalDnsService.Interface {
}
}
metered = profile.metered
active = true // possible race condition here?
if (Build.VERSION.SDK_INT >= 22) builder.setUnderlyingNetworks(underlyingNetworks)
......
......@@ -29,14 +29,15 @@ import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.migration.RecreateSchemaMigration
import com.github.shadowsocks.utils.Key
@Database(entities = [Profile::class, KeyValuePair::class], version = 27)
@Database(entities = [Profile::class, KeyValuePair::class], version = 28)
abstract class PrivateDatabase : RoomDatabase() {
companion object {
private val instance by lazy {
Room.databaseBuilder(app, PrivateDatabase::class.java, Key.DB_PROFILE)
.addMigrations(
Migration26,
Migration27
Migration27,
Migration28
)
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
......@@ -61,4 +62,8 @@ abstract class PrivateDatabase : RoomDatabase() {
override fun migrate(database: SupportSQLiteDatabase) =
database.execSQL("ALTER TABLE `Profile` ADD COLUMN `udpFallback` INTEGER")
}
object Migration28 : Migration(27, 28) {
override fun migrate(database: SupportSQLiteDatabase) =
database.execSQL("ALTER TABLE `Profile` ADD COLUMN `metered` INTEGER NOT NULL DEFAULT 0")
}
}
......@@ -20,6 +20,7 @@
package com.github.shadowsocks.database
import android.annotation.TargetApi
import android.net.Uri
import android.os.Parcelable
import android.util.Base64
......@@ -58,6 +59,8 @@ data class Profile(
var bypass: Boolean = false,
var udpdns: Boolean = false,
var ipv6: Boolean = true,
@TargetApi(28)
var metered: Boolean = false,
var individual: String = "",
var tx: Long = 0,
var rx: Long = 0,
......@@ -70,7 +73,7 @@ data class Profile(
) : Parcelable, Serializable {
companion object {
private const val TAG = "ShadowParser"
private const val serialVersionUID = 0L
private const val serialVersionUID = 1L
private val pattern =
"""(?i)ss://[-a-zA-Z0-9+&@#/%?=.~*'()|!:,;\[\]]*[-a-zA-Z0-9+&@#/%=.~*'()|\[\]]""".toRegex()
private val userInfoPattern = "^(.+?):(.*)$".toRegex()
......@@ -156,6 +159,7 @@ data class Profile(
if (fallback) return@apply
remoteDns = json.optString("remote_dns", remoteDns)
ipv6 = json.optBoolean("ipv6", ipv6)
metered = json.optBoolean("metered", metered)
json.optJSONObject("proxy_apps")?.also {
proxyApps = it.optBoolean("enabled", proxyApps)
bypass = it.optBoolean("bypass", bypass)
......@@ -232,6 +236,7 @@ data class Profile(
fun copyFeatureSettingsTo(profile: Profile) {
profile.route = route
profile.ipv6 = ipv6
profile.metered = metered
profile.proxyApps = proxyApps
profile.bypass = bypass
profile.individual = individual
......@@ -269,6 +274,7 @@ data class Profile(
put("route", route)
put("remote_dns", remoteDns)
put("ipv6", ipv6)
put("metered", metered)
put("proxy_apps", JSONObject().apply {
put("enabled", proxyApps)
if (proxyApps) {
......@@ -295,6 +301,7 @@ data class Profile(
DataStore.bypass = bypass
DataStore.privateStore.putBoolean(Key.udpdns, udpdns)
DataStore.privateStore.putBoolean(Key.ipv6, ipv6)
DataStore.privateStore.putBoolean(Key.metered, metered)
DataStore.individual = individual
DataStore.plugin = plugin ?: ""
DataStore.udpFallback = udpFallback
......@@ -315,6 +322,7 @@ data class Profile(
bypass = DataStore.bypass
udpdns = DataStore.privateStore.getBoolean(Key.udpdns, false)
ipv6 = DataStore.privateStore.getBoolean(Key.ipv6, false)
metered = DataStore.privateStore.getBoolean(Key.metered, false)
individual = DataStore.individual
plugin = DataStore.plugin
udpFallback = DataStore.udpFallback
......
......@@ -42,7 +42,7 @@ object DataStore : OnPreferenceDataStoreChangeListener {
publicStore.registerChangeListener(this)
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) {
Key.id -> if (DataStore.directBootAware) DirectBoot.update()
}
......
......@@ -23,5 +23,5 @@ package com.github.shadowsocks.preference
import androidx.preference.PreferenceDataStore
interface OnPreferenceDataStoreChangeListener {
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?)
fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String)
}
/*******************************************************************************
* *
* Copyright (C) 2019 by Max Lv <max.c.lv@gmail.com> *
* Copyright (C) 2019 by Mygod Studio <contact-shadowsocks-android@mygod.be> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
package com.github.shadowsocks.preference
import android.text.InputFilter
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.preference.EditTextPreference
object PortPreferenceListener : EditTextPreference.OnBindEditTextListener {
private val portLengthFilter = arrayOf(InputFilter.LengthFilter(5))
override fun onBindEditText(editText: EditText) {
editText.inputType = EditorInfo.TYPE_CLASS_NUMBER
editText.filters = portLengthFilter
editText.setSingleLine()
}
}
......@@ -50,6 +50,7 @@ object Key {
const val bypass = "isBypassApps"
const val udpdns = "isUdpDns"
const val ipv6 = "isIpv6"
const val metered = "metered"
const val host = "proxy"
const val password = "sitekey"
......
......@@ -36,6 +36,8 @@
<!-- feature category -->
<string name="ipv6">IPv6 Route</string>
<string name="ipv6_summary">Redirect IPv6 traffic to remote</string>
<string name="metered">Metered Hint</string>
<string name="metered_summary">Hint system to treat VPN as metered</string>
<string name="route_list">Route</string>
<string name="route_entry_all">All</string>
<string name="route_entry_bypass_lan">Bypass LAN</string>
......
......@@ -11,7 +11,7 @@
# The setting is particularly useful for tweaking memory settings.
android.enableJetifier=true
android.enableR8=true
# android.enableR8.fullMode=true
android.enableR8.fullMode=true
android.useAndroidX=true
# When configured, Gradle will run in incubating parallel mode.
......
......@@ -56,11 +56,11 @@ androidExtensions {
dependencies {
implementation project(':core')
implementation "androidx.browser:browser:1.0.0"
implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.gms:play-services-vision:17.0.2'
implementation 'com.google.firebase:firebase-ads:17.2.0'
implementation "com.takisoft.preferencex:preferencex-simplemenu:$preferencexVersion"
implementation 'com.takisoft.preferencex:preferencex-simplemenu:1.0.0'
implementation 'com.twofortyfouram:android-plugin-api-for-locale:1.0.4'
implementation 'net.glxn.qrgen:android:2.0'
implementation 'xyz.belvi.mobilevision:barcodescanner:2.0.3'
......
......@@ -23,17 +23,12 @@ package com.github.shadowsocks
import android.app.Application
import android.content.res.Configuration
import androidx.appcompat.app.AppCompatDelegate
import com.github.shadowsocks.preference.BottomSheetPreferenceDialogFragment
import com.github.shadowsocks.preference.IconListPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
class App : Application() {
override fun onCreate() {
super.onCreate()
Core.init(this, MainActivity::class)
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true)
PreferenceFragmentCompat.registerPreferenceFragment(IconListPreference::class.java,
BottomSheetPreferenceDialogFragment::class.java)
}
override fun onConfigurationChanged(newConfig: Configuration) {
......
......@@ -22,22 +22,24 @@ package com.github.shadowsocks
import android.os.Build
import android.os.Bundle
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.github.shadowsocks.bg.BaseService
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.PortPreferenceListener
import com.github.shadowsocks.utils.remove
import com.takisoft.preferencex.PreferenceFragmentCompat
class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_global)
val boot = findPreference(Key.isAutoConnect) as SwitchPreference
val boot = findPreference<SwitchPreference>(Key.isAutoConnect)!!
boot.setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
......@@ -45,13 +47,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
boot.isChecked = BootReceiver.enabled
if (Build.VERSION.SDK_INT >= 24) boot.setSummary(R.string.auto_connect_summary_v24)
val canToggleLocked = findPreference(Key.directBootAware)
val canToggleLocked = findPreference<Preference>(Key.directBootAware)!!
if (Build.VERSION.SDK_INT >= 24) canToggleLocked.setOnPreferenceChangeListener { _, newValue ->
if (Core.directBootSupported && newValue as Boolean) DirectBoot.update() else DirectBoot.clean()
true
} else canToggleLocked.remove()
val tfo = findPreference(Key.tfo) as SwitchPreference
val tfo = findPreference<SwitchPreference>(Key.tfo)!!
tfo.isChecked = DataStore.tcpFastOpen
tfo.setOnPreferenceChangeListener { _, value ->
if (value as Boolean && !TcpFastOpen.sendEnabled) {
......@@ -68,10 +70,13 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() {
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
}
val serviceMode = findPreference(Key.serviceMode)
val portProxy = findPreference(Key.portProxy)
val portLocalDns = findPreference(Key.portLocalDns)
val portTransproxy = findPreference(Key.portTransproxy)
val serviceMode = findPreference<Preference>(Key.serviceMode)!!
val portProxy = findPreference<EditTextPreference>(Key.portProxy)!!
portProxy.onBindEditTextListener = PortPreferenceListener
val portLocalDns = findPreference<EditTextPreference>(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = PortPreferenceListener
val portTransproxy = findPreference<EditTextPreference>(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = PortPreferenceListener
val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false)
......
......@@ -209,7 +209,7 @@ class MainActivity : AppCompatActivity(), ShadowsocksConnection.Callback, OnPref
else ImportProfilesDialogFragment().withArg(ProfilesArg(profiles)).show(supportFragmentManager, null)
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) {
Key.serviceMode -> handler.post {
connection.disconnect(this)
......
......@@ -24,34 +24,26 @@ import android.app.Activity
import android.content.BroadcastReceiver
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.SwitchPreference
import androidx.preference.*
import com.github.shadowsocks.Core.app
import com.github.shadowsocks.database.Profile
import com.github.shadowsocks.database.ProfileManager
import com.github.shadowsocks.plugin.*
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.IconListPreference
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PluginConfigurationDialogFragment
import com.github.shadowsocks.utils.Action
import com.github.shadowsocks.utils.DirectBoot
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.readableMessage
import com.github.shadowsocks.preference.*
import com.github.shadowsocks.utils.*
import com.google.android.material.snackbar.Snackbar
import com.takisoft.preferencex.EditTextPreference
import com.takisoft.preferencex.PreferenceFragmentCompat
import kotlinx.android.parcel.Parcelize
class ProfileConfigFragment : PreferenceFragmentCompat(),
Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener {
companion object {
companion object PasswordSummaryProvider : Preference.SummaryProvider<EditTextPreference> {
override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0)
private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1
const val REQUEST_UNSAVED_CHANGES = 2
}
......@@ -77,23 +69,29 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
private lateinit var receiver: BroadcastReceiver
private lateinit var udpFallback: Preference
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.preferenceDataStore = DataStore.privateStore
val activity = requireActivity()
profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L)
addPreferencesFromResource(R.xml.pref_profile)
findPreference<EditTextPreference>(Key.remotePort)!!.onBindEditTextListener = PortPreferenceListener
findPreference<EditTextPreference>(Key.password)!!.summaryProvider = PasswordSummaryProvider
val serviceMode = DataStore.serviceMode
findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy
isProxyApps = findPreference(Key.proxyApps) as SwitchPreference
findPreference<Preference>(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy
findPreference<Preference>(Key.ipv6)!!.isEnabled = serviceMode == Key.modeVpn
isProxyApps = findPreference(Key.proxyApps)!!
isProxyApps.isEnabled = serviceMode == Key.modeVpn
isProxyApps.setOnPreferenceClickListener {
startActivity(Intent(activity, AppManager::class.java))
isProxyApps.isChecked = true
false
}
findPreference(Key.udpdns).isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin) as IconListPreference
pluginConfigure = findPreference(Key.pluginConfigure) as EditTextPreference
findPreference<Preference>(Key.metered)!!.apply {
if (Build.VERSION.SDK_INT >= 28) isEnabled = serviceMode == Key.modeVpn else remove()
}
findPreference<Preference>(Key.udpdns)!!.isEnabled = serviceMode != Key.modeProxy
plugin = findPreference(Key.plugin)!!
pluginConfigure = findPreference(Key.pluginConfigure)!!
plugin.unknownValueSummary = getString(R.string.plugin_unknown)
plugin.setOnPreferenceChangeListener { _, newValue ->
pluginConfiguration = PluginConfiguration(pluginConfiguration.pluginsOptions, newValue as String)
......@@ -109,7 +107,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
pluginConfigure.onPreferenceChangeListener = this
initPlugins()
receiver = Core.listenForPackageChanges(false) { initPlugins() }
udpFallback = findPreference(Key.udpFallback)
udpFallback = findPreference(Key.udpFallback)!!
DataStore.privateStore.registerChangeListener(this)
}
......@@ -122,14 +120,16 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
pluginConfiguration = PluginConfiguration(DataStore.plugin)
plugin.value = pluginConfiguration.selected
plugin.init()
plugin.checkSummary()
pluginConfigure.isEnabled = pluginConfiguration.selected.isNotEmpty()
pluginConfigure.text = pluginConfiguration.selectedOptions.toString()
}
private fun showPluginEditor() = displayPreferenceDialog(PluginConfigurationDialogFragment(), Key.pluginConfigure,
bundleOf(Pair("key", Key.pluginConfigure),
Pair(PluginConfigurationDialogFragment.PLUGIN_ID_FRAGMENT_TAG, pluginConfiguration.selected)))
private fun showPluginEditor() {
PluginConfigurationDialogFragment().apply {
setArg(Key.pluginConfigure, pluginConfiguration.selected)
setTargetFragment(this@ProfileConfigFragment, 0)
}.show(fragmentManager ?: return, Key.pluginConfigure)
}
private fun saveAndExit() {
val profile = ProfileManager.getProfile(profileId) ?: Profile()
......@@ -161,18 +161,26 @@ class ProfileConfigFragment : PreferenceFragmentCompat(),
false
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
if (key != Key.proxyApps && findPreference<Preference>(key) != null) DataStore.dirty = true
}
override fun onDisplayPreferenceDialog(preference: Preference) {
if (preference.key == Key.pluginConfigure) {
when (preference.key) {
Key.plugin -> BottomSheetPreferenceDialogFragment().apply {
setArg(Key.plugin)
setTargetFragment(this@ProfileConfigFragment, 0)
}.show(fragmentManager ?: return, Key.plugin)
Key.pluginConfigure -> {
val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE)
if (intent.resolveActivity(requireContext().packageManager) == null) showPluginEditor() else
if (intent.resolveActivity(requireContext().packageManager) == null) showPluginEditor() else {
startActivityForResult(intent
.putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString()),
REQUEST_CODE_PLUGIN_CONFIGURE)
} else super.onDisplayPreferenceDialog(preference)
}
}
else -> super.onDisplayPreferenceDialog(preference)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
......
......@@ -32,6 +32,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.preference.PreferenceDialogFragmentCompat
import androidx.recyclerview.widget.DefaultItemAnimator
......@@ -97,6 +98,10 @@ class BottomSheetPreferenceDialogFragment : PreferenceDialogFragmentCompat() {
}
}
fun setArg(key: String) {
arguments = bundleOf(PreferenceDialogFragmentCompat.ARG_KEY to key)
}
private val preference by lazy { getPreference() as IconListPreference }
private var clickedIndex = -1
......
......@@ -26,6 +26,14 @@ import android.util.AttributeSet
import androidx.preference.ListPreference
class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPreference(context, attrs) {
companion object FallbackProvider : SummaryProvider<IconListPreference> {
override fun provideSummary(preference: IconListPreference?): CharSequence? {
val i = preference?.selectedEntry
return if (i != null && i < 0) preference.unknownValueSummary?.format(preference.value) else
preference?.entry
}
}
var entryIcons: Array<Drawable?>? = null
val selectedEntry: Int get() = entryValues.indexOf(value)
val entryIcon: Drawable? get() = entryIcons?.getOrNull(selectedEntry)
......@@ -49,7 +57,6 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
val listener = listener
if (listener == null || listener.onPreferenceChange(preference, newValue)) {
value = newValue.toString()
checkSummary()
if (entryIcons != null) icon = entryIcon
true
} else false
......@@ -60,13 +67,9 @@ class IconListPreference(context: Context, attrs: AttributeSet? = null) : ListPr
// a.recycle()
}
fun checkSummary() {
val unknownValueSummary = unknownValueSummary
if (unknownValueSummary != null) summary = if (selectedEntry < 0) unknownValueSummary.format(value) else "%s"
}
fun init() {
icon = entryIcon
summaryProvider = FallbackProvider
}
override fun onSetInitialValue(defaultValue: Any?) {
super.onSetInitialValue(defaultValue)
......
......@@ -23,17 +23,23 @@ package com.github.shadowsocks.preference
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.preference.EditTextPreferenceDialogFragmentCompat
import androidx.preference.PreferenceDialogFragmentCompat
import com.github.shadowsocks.ProfileConfigActivity
import com.github.shadowsocks.plugin.PluginContract
import com.github.shadowsocks.plugin.PluginManager
import com.takisoft.preferencex.EditTextPreferenceDialogFragmentCompat
class PluginConfigurationDialogFragment : EditTextPreferenceDialogFragmentCompat() {
companion object {
const val PLUGIN_ID_FRAGMENT_TAG =
private const val PLUGIN_ID_FRAGMENT_TAG =
"com.github.shadowsocks.preference.PluginConfigurationDialogFragment.PLUGIN_ID"
}
fun setArg(key: String, plugin: String) {
arguments = bundleOf(PreferenceDialogFragmentCompat.ARG_KEY to key, PLUGIN_ID_FRAGMENT_TAG to plugin)
}
private lateinit var editText: EditText
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
......
<vector android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"
android:tint="?attr/colorControlNormal">
<path android:fillColor="#FF000000" android:pathData="M13,2.05v3.03c3.39,0.49 6,3.39 6,6.92 0,0.9 -0.18,1.75 -0.48,2.54l2.6,1.53c0.56,-1.24 0.88,-2.62 0.88,-4.07 0,-5.18 -3.95,-9.45 -9,-9.95zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-3.53 2.61,-6.43 6,-6.92V2.05c-5.06,0.5 -9,4.76 -9,9.95 0,5.52 4.47,10 9.99,10 3.31,0 6.24,-1.61 8.06,-4.09l-2.6,-1.53C16.17,17.98 14.21,19 12,19z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<!-- Based on: https://android.googlesource.com/platform/frameworks/support/+/9b8fb51/preference/res/layout/preference_dialog_edittext.xml -->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="48dp"
android:layout_marginBottom="48dp"
android:overScrollMode="ifContentScrolls">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@android:id/message"
style="?android:attr/textAppearanceSmall"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="48dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"/>
<com.google.android.material.textfield.TextInputLayout
android:orientation="vertical"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@android:id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:inputType="textPassword"
android:typeface="monospace"/>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
</ScrollView>
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:initialExpandedChildrenCount="3">
<SwitchPreference android:key="isAutoConnect"
android:persistent="false"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
<SwitchPreference
android:key="directBootAware"
android:icon="@drawable/ic_action_lock"
android:summary="@string/direct_boot_aware_summary"
android:title="@string/direct_boot_aware"/>
app:key="isAutoConnect"
app:persistent="false"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect"/>
<SwitchPreference
android:key="tcp_fastopen"
android:icon="@drawable/ic_action_offline_bolt"
android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/>
app:key="directBootAware"
app:icon="@drawable/ic_action_lock"
app:summary="@string/direct_boot_aware_summary"
app:title="@string/direct_boot_aware"/>
<SwitchPreference
app:key="tcp_fastopen"
app:icon="@drawable/ic_action_offline_bolt"
app:summary="@string/tcp_fastopen_summary"
app:title="TCP Fast Open"/>
<SimpleMenuPreference
android:key="serviceMode"
android:entries="@array/service_modes"
android:entryValues="@array/service_mode_values"
android:defaultValue="vpn"
android:summary="%s"
android:title="@string/service_mode"/>
<AutoSummaryEditTextPreference
android:key="portProxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_proxy"/>
<AutoSummaryEditTextPreference
android:key="portLocalDns"
android:icon="@drawable/ic_action_dns"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_local_dns"/>
<AutoSummaryEditTextPreference
android:key="portTransproxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_transproxy"/>
<com.takisoft.preferencex.SimpleMenuPreference
app:key="serviceMode"
app:entries="@array/service_modes"
app:entryValues="@array/service_mode_values"
app:defaultValue="vpn"
app:title="@string/service_mode"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="portProxy"
app:title="@string/port_proxy"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="portLocalDns"
app:icon="@drawable/ic_action_dns"
app:title="@string/port_local_dns"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="portTransproxy"
app:title="@string/port_transproxy"
app:useSimpleSummaryProvider="true"/>
</PreferenceScreen>
......@@ -2,95 +2,93 @@
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<AutoSummaryEditTextPreference
android:key="profileName"
app:pref_summaryHasText="%s"
android:title="@string/profile_name"/>
<EditTextPreference
app:key="profileName"
app:title="@string/profile_name"
app:useSimpleSummaryProvider="true"/>
<PreferenceCategory
android:title="@string/proxy_cat">
app:title="@string/proxy_cat">
<AutoSummaryEditTextPreference
android:key="proxy"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/proxy"/>
<AutoSummaryEditTextPreference
android:key="remotePortNum"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/remote_port"/>
<AutoSummaryEditTextPreference
android:inputType="textPassword"
android:typeface="monospace"
app:pref_summaryPasswordSubstituteLength="0"
android:key="sitekey"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/sitekey"/>
<SimpleMenuPreference
android:key="encMethod"
android:icon="@drawable/ic_notification_enhanced_encryption"
android:entries="@array/enc_method_entry"
android:entryValues="@array/enc_method_value"
android:summary="%s"
android:title="@string/enc_method"/>
<EditTextPreference
app:key="proxy"
app:singleLine="true"
app:title="@string/proxy"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="remotePortNum"
app:title="@string/remote_port"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:dialogLayout="@layout/preference_dialog_password"
app:key="sitekey"
app:title="@string/sitekey"/>
<com.takisoft.preferencex.SimpleMenuPreference
app:key="encMethod"
app:icon="@drawable/ic_notification_enhanced_encryption"
app:entries="@array/enc_method_entry"
app:entryValues="@array/enc_method_value"
app:title="@string/enc_method"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/feature_cat">
app:title="@string/feature_cat">
<SimpleMenuPreference
android:key="route"
android:entries="@array/route_entry"
android:entryValues="@array/route_value"
android:summary="%s"
android:title="@string/route_list"/>
<AutoSummaryEditTextPreference
android:key="remoteDns"
android:icon="@drawable/ic_action_dns"
android:title="@string/remote_dns"
app:pref_summaryHasText="%s"/>
<com.takisoft.preferencex.SimpleMenuPreference
app:key="route"
app:entries="@array/route_entry"
app:entryValues="@array/route_value"
app:title="@string/route_list"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="remoteDns"
app:icon="@drawable/ic_action_dns"
app:title="@string/remote_dns"
app:useSimpleSummaryProvider="true"/>
<SwitchPreference
android:key="isIpv6"
android:icon="@drawable/ic_image_looks_6"
android:summary="@string/ipv6_summary"
android:title="@string/ipv6"/>
app:key="isIpv6"
app:icon="@drawable/ic_image_looks_6"
app:summary="@string/ipv6_summary"
app:title="@string/ipv6"/>
<SwitchPreference
android:key="isProxyApps"
android:icon="@drawable/ic_navigation_apps"
android:summary="@string/proxied_apps_summary"
android:title="@string/proxied_apps"/>
app:key="isProxyApps"
app:icon="@drawable/ic_navigation_apps"
app:summary="@string/proxied_apps_summary"
app:title="@string/proxied_apps"/>
<SwitchPreference
android:key="isUdpDns"
android:icon="@drawable/ic_action_dns"
android:summary="@string/udp_dns_summary"
android:title="@string/udp_dns"/>
app:key="isUdpDns"
app:icon="@drawable/ic_action_dns"
app:summary="@string/udp_dns_summary"
app:title="@string/udp_dns"/>
<SwitchPreference
app:key="metered"
app:icon="@drawable/ic_device_data_usage"
app:summary="@string/metered_summary"
app:title="@string/metered"/>
</PreferenceCategory>
<PreferenceCategory
android:title="@string/plugin">
app:title="@string/plugin">
<com.github.shadowsocks.preference.IconListPreference
android:key="plugin"
android:persistent="false"
android:summary="%s"
android:title="@string/plugin"/>
<AutoSummaryEditTextPreference
android:key="plugin.configure"
android:icon="@drawable/ic_action_settings"
android:persistent="false"
android:singleLine="true"
app:pref_summaryHasText="%s"
android:title="@string/plugin_configure"/>
app:key="plugin"
app:persistent="false"
app:title="@string/plugin"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="plugin.configure"
app:icon="@drawable/ic_action_settings"
app:persistent="false"
app:singleLine="true"
app:title="@string/plugin_configure"
app:useSimpleSummaryProvider="true"/>
<Preference
android:key="udpFallback"
android:title="@string/udp_fallback"
android:summary="@string/plugin_disabled">
app:key="udpFallback"
app:title="@string/udp_fallback"
app:summary="@string/plugin_disabled">
<intent android:targetPackage="com.github.shadowsocks"
android:targetClass="com.github.shadowsocks.UdpFallbackProfileActivity"/>
</Preference>
......
......@@ -20,11 +20,7 @@
package com.github.shadowsocks.tv
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.updateLayoutParams
import androidx.leanback.preference.LeanbackPreferenceDialogFragmentCompat
import androidx.leanback.preference.LeanbackSettingsFragmentCompat
import androidx.preference.*
......@@ -59,11 +55,4 @@ class MainFragment : LeanbackSettingsFragmentCompat() {
}
return super.onPreferenceDisplayDialog(caller, pref)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<View>(R.id.settings_preference_fragment_container).updateLayoutParams {
width = ViewGroup.LayoutParams.MATCH_PARENT
}
}
}
......@@ -35,10 +35,7 @@ import androidx.leanback.preference.LeanbackPreferenceFragmentCompat
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.lifecycle.get
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.SwitchPreference
import androidx.preference.*
import com.crashlytics.android.Crashlytics
import com.github.shadowsocks.BootReceiver
import com.github.shadowsocks.Core
......@@ -51,6 +48,7 @@ import com.github.shadowsocks.net.HttpsTest
import com.github.shadowsocks.net.TcpFastOpen
import com.github.shadowsocks.preference.DataStore
import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener
import com.github.shadowsocks.preference.PortPreferenceListener
import com.github.shadowsocks.utils.Key
import com.github.shadowsocks.utils.datas
import com.github.shadowsocks.utils.printLog
......@@ -71,9 +69,9 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
private lateinit var serviceMode: Preference
private lateinit var tfo: SwitchPreference
private lateinit var shareOverLan: Preference
private lateinit var portProxy: Preference
private lateinit var portLocalDns: Preference
private lateinit var portTransproxy: Preference
private lateinit var portProxy: EditTextPreference
private lateinit var portLocalDns: EditTextPreference
private lateinit var portTransproxy: EditTextPreference
private val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue ->
val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) {
Key.modeProxy -> Pair(false, false)
......@@ -155,12 +153,12 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
preferenceManager.preferenceDataStore = DataStore.publicStore
DataStore.initGlobal()
addPreferencesFromResource(R.xml.pref_main)
fab = findPreference(Key.id) as ListPreference
fab = findPreference(Key.id)!!
populateProfiles()
stats = findPreference(Key.controlStats)
controlImport = findPreference(Key.controlImport)
stats = findPreference(Key.controlStats)!!
controlImport = findPreference(Key.controlImport)!!
(findPreference(Key.isAutoConnect) as SwitchPreference).apply {
findPreference<SwitchPreference>(Key.isAutoConnect)!!.apply {
setOnPreferenceChangeListener { _, value ->
BootReceiver.enabled = value as Boolean
true
......@@ -168,7 +166,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
isChecked = BootReceiver.enabled
}
tfo = findPreference(Key.tfo) as SwitchPreference
tfo = findPreference(Key.tfo)!!
tfo.isChecked = DataStore.tcpFastOpen
tfo.setOnPreferenceChangeListener { _, value ->
if (value as Boolean && !TcpFastOpen.sendEnabled) {
......@@ -185,13 +183,16 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version"))
}
serviceMode = findPreference(Key.serviceMode)
shareOverLan = findPreference(Key.shareOverLan)
portProxy = findPreference(Key.portProxy)
portLocalDns = findPreference(Key.portLocalDns)
portTransproxy = findPreference(Key.portTransproxy)
serviceMode = findPreference(Key.serviceMode)!!
shareOverLan = findPreference(Key.shareOverLan)!!
portProxy = findPreference(Key.portProxy)!!
portProxy.onBindEditTextListener = PortPreferenceListener
portLocalDns = findPreference(Key.portLocalDns)!!
portLocalDns.onBindEditTextListener = PortPreferenceListener
portTransproxy = findPreference(Key.portTransproxy)!!
portTransproxy.onBindEditTextListener = PortPreferenceListener
serviceMode.onPreferenceChangeListener = onServiceModeChange
findPreference(Key.about).apply {
findPreference<Preference>(Key.about)!!.apply {
summary = getString(R.string.about_title, BuildConfig.VERSION_NAME)
setOnPreferenceClickListener {
Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show()
......@@ -235,7 +236,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo
}
}
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) {
override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) {
when (key) {
Key.serviceMode -> handler.post {
connection.disconnect(requireContext())
......
......@@ -5,6 +5,9 @@
<item name="android:colorButtonNormal">@color/material_accent_200</item>
<item name="android:colorPrimary">@color/color_primary</item>
<item name="android:colorPrimaryDark">@color/color_primary_dark</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Leanback</item>
</style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="@string/app_name">
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/app_name">
<!-- profileId is stored in privateStore so its value needs to be populated manually -->
<ListPreference
android:key="profileId"
android:title="@string/connect"
android:summary="%s"
android:persistent="false"/>
app:key="profileId"
app:title="@string/connect"
app:persistent="false"
app:useSimpleSummaryProvider="true"/>
<Preference
android:key="control.stats"
android:title="@string/connection_test_pending"
android:summary="@string/stat_summary"/>
app:key="control.stats"
app:title="@string/connection_test_pending"
app:summary="@string/stat_summary"/>
<Preference
android:key="control.import"
android:title="@string/action_replace_file"/>
app:key="control.import"
app:title="@string/action_replace_file"/>
<Preference
android:key="control.export"
android:title="@string/action_export_file"/>
app:key="control.export"
app:title="@string/action_export_file"/>
<PreferenceCategory
android:title="@string/settings"
app:title="@string/settings"
app:initialExpandedChildrenCount="2">
<SwitchPreference
android:key="isAutoConnect"
android:persistent="false"
android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"/>
app:key="isAutoConnect"
app:persistent="false"
app:summary="@string/auto_connect_summary"
app:title="@string/auto_connect"/>
<SwitchPreference
android:key="tcp_fastopen"
android:summary="@string/tcp_fastopen_summary"
android:title="TCP Fast Open"/>
app:key="tcp_fastopen"
app:summary="@string/tcp_fastopen_summary"
app:title="TCP Fast Open"/>
<ListPreference
android:key="serviceMode"
android:entries="@array/service_modes"
android:entryValues="@array/service_mode_values"
android:defaultValue="vpn"
android:summary="%s"
android:title="@string/service_mode"/>
app:key="serviceMode"
app:entries="@array/service_modes"
app:entryValues="@array/service_mode_values"
app:defaultValue="vpn"
app:title="@string/service_mode"
app:useSimpleSummaryProvider="true"/>
<SwitchPreference
android:key="shareOverLan"
android:title="@string/share_over_lan"/>
<com.takisoft.preferencex.AutoSummaryEditTextPreference
android:key="portProxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_proxy"/>
<com.takisoft.preferencex.AutoSummaryEditTextPreference
android:key="portLocalDns"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_local_dns"/>
<com.takisoft.preferencex.AutoSummaryEditTextPreference
android:key="portTransproxy"
android:inputType="number"
android:maxLength="5"
app:pref_summaryHasText="%s"
android:singleLine="true"
android:title="@string/port_transproxy"/>
app:key="shareOverLan"
app:title="@string/share_over_lan"/>
<EditTextPreference
app:key="portProxy"
app:title="@string/port_proxy"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="portLocalDns"
app:title="@string/port_local_dns"
app:useSimpleSummaryProvider="true"/>
<EditTextPreference
app:key="portTransproxy"
app:title="@string/port_transproxy"
app:useSimpleSummaryProvider="true"/>
</PreferenceCategory>
<Preference
android:key="about"
android:title="@string/about"/>
app:key="about"
app:title="@string/about"/>
</PreferenceScreen>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment