From 0939f5b4b81a03b107a9cc03350f503d7a541469 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 12 Feb 2019 23:05:25 +0800 Subject: [PATCH 01/14] Migrate to androidx.preference 1.1.0 Also allows show password. See also #1948. --- build.gradle | 1 - core/build.gradle | 3 +- .../preference/PortPreferenceListener.kt | 36 +++++ gradle.properties | 2 +- mobile/build.gradle | 4 +- .../main/java/com/github/shadowsocks/App.kt | 5 - .../GlobalSettingsPreferenceFragment.kt | 23 +-- .../shadowsocks/ProfileConfigFragment.kt | 66 +++++---- .../BottomSheetPreferenceDialogFragment.kt | 5 + .../preference/IconListPreference.kt | 15 +- .../PluginConfigurationDialogFragment.kt | 10 +- .../res/layout/preference_dialog_password.xml | 44 ++++++ mobile/src/main/res/xml/pref_global.xml | 77 +++++----- mobile/src/main/res/xml/pref_profile.xml | 135 +++++++++--------- plugin/build.gradle | 4 +- .../shadowsocks/tv/MainPreferenceFragment.kt | 23 +-- tv/src/main/res/xml/pref_main.xml | 97 ++++++------- 17 files changed, 312 insertions(+), 238 deletions(-) create mode 100644 core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt create mode 100644 mobile/src/main/res/layout/preference_dialog_password.xml diff --git a/build.gradle b/build.gradle index d781942c97..1ec7a98dee 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,6 @@ buildscript { lifecycleVersion = '2.0.0' roomVersion = '2.0.0' workVersion = '1.0.0-beta05' - preferencexVersion = '1.0.0' junitVersion = '4.12' androidTestVersion = '1.1.1' androidEspressoVersion = '3.1.1' diff --git a/core/build.gradle b/core/build.gradle index d21bc2d811..f579e87592 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -43,12 +43,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-alpha03' api "androidx.room:room-runtime:$roomVersion" api 'com.crashlytics.sdk.android:crashlytics:2.9.9' api 'com.google.firebase:firebase-config:16.3.0' 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' diff --git a/core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt b/core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt new file mode 100644 index 0000000000..8e2a41749e --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt @@ -0,0 +1,36 @@ +/******************************************************************************* + * * + * Copyright (C) 2019 by Max Lv * + * Copyright (C) 2019 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +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() + } +} diff --git a/gradle.properties b/gradle.properties index f87dc1290e..aac8046a69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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. diff --git a/mobile/build.gradle b/mobile/build.gradle index d9ea8c2ae6..3cf1a50ed3 100644 --- a/mobile/build.gradle +++ b/mobile/build.gradle @@ -51,10 +51,10 @@ android { dependencies { implementation project(':core') - implementation "androidx.browser:browser:1.0.0" + implementation 'androidx.browser:browser:1.0.0' implementation 'com.google.android.gms:play-services-vision:17.0.2' implementation 'com.google.firebase:firebase-ads:17.1.3' - 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' diff --git a/mobile/src/main/java/com/github/shadowsocks/App.kt b/mobile/src/main/java/com/github/shadowsocks/App.kt index 399bcb07b2..13c58c9182 100644 --- a/mobile/src/main/java/com/github/shadowsocks/App.kt +++ b/mobile/src/main/java/com/github/shadowsocks/App.kt @@ -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) { diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index 4fddc5a78d..3911c05538 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -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(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(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(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(Key.serviceMode) + val portProxy = findPreference(Key.portProxy) + portProxy.onBindEditTextListener = PortPreferenceListener + val portLocalDns = findPreference(Key.portLocalDns) + portLocalDns.onBindEditTextListener = PortPreferenceListener + val portTransproxy = findPreference(Key.portTransproxy) + portTransproxy.onBindEditTextListener = PortPreferenceListener val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { Key.modeProxy -> Pair(false, false) diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index 9e0d4cbe3b..4adac29e9d 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -26,10 +26,7 @@ import android.content.Intent import android.os.Bundle 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 @@ -37,20 +34,17 @@ import com.github.shadowsocks.plugin.PluginConfiguration import com.github.shadowsocks.plugin.PluginContract import com.github.shadowsocks.plugin.PluginManager import com.github.shadowsocks.plugin.PluginOptions -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.preference.* import com.github.shadowsocks.utils.Action import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.Key import com.google.android.material.snackbar.Snackbar -import com.takisoft.preferencex.EditTextPreference -import com.takisoft.preferencex.PreferenceFragmentCompat class ProfileConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener { - companion object { + private companion object PasswordSummaryProvider : Preference.SummaryProvider { + override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0) + private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 } @@ -62,23 +56,25 @@ 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(Key.remotePort).onBindEditTextListener = PortPreferenceListener + findPreference(Key.password).summaryProvider = PasswordSummaryProvider val serviceMode = DataStore.serviceMode - findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy - isProxyApps = findPreference(Key.proxyApps) as SwitchPreference + findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy + 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(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) @@ -107,14 +103,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) + } fun saveAndExit() { val profile = ProfileManager.getProfile(profileId) ?: Profile() @@ -147,17 +145,27 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), } override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { - if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true + if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true } override fun onDisplayPreferenceDialog(preference: Preference) { - if (preference.key == Key.pluginConfigure) { - val intent = PluginManager.buildIntent(pluginConfiguration.selected, PluginContract.ACTION_CONFIGURE) - 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) + 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 { + startActivityForResult(intent + .putExtra(PluginContract.EXTRA_OPTIONS, pluginConfiguration.selectedOptions.toString()), + REQUEST_CODE_PLUGIN_CONFIGURE) + } + } + else -> super.onDisplayPreferenceDialog(preference) + } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt index 9f0f226cf2..487bbf60e0 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/BottomSheetPreferenceDialogFragment.kt @@ -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 diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/IconListPreference.kt b/mobile/src/main/java/com/github/shadowsocks/preference/IconListPreference.kt index ac9b2c4d1d..76718a3ee2 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/IconListPreference.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/IconListPreference.kt @@ -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 { + 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? = 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) diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.kt index 253397717f..902fd70202 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/PluginConfigurationDialogFragment.kt @@ -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) { diff --git a/mobile/src/main/res/layout/preference_dialog_password.xml b/mobile/src/main/res/layout/preference_dialog_password.xml new file mode 100644 index 0000000000..1dfe01de86 --- /dev/null +++ b/mobile/src/main/res/layout/preference_dialog_password.xml @@ -0,0 +1,44 @@ + + + + + + + + + + diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index d7199b27cd..953aed77b9 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -1,49 +1,40 @@ - - + app:key="isAutoConnect" + app:persistent="false" + app:summary="@string/auto_connect_summary" + app:title="@string/auto_connect"/> + app:key="directBootAware" + app:icon="@drawable/ic_action_lock" + app:summary="@string/direct_boot_aware_summary" + app:title="@string/direct_boot_aware"/> + - - - - + + + + diff --git a/mobile/src/main/res/xml/pref_profile.xml b/mobile/src/main/res/xml/pref_profile.xml index 626d333c0e..89149f50ab 100644 --- a/mobile/src/main/res/xml/pref_profile.xml +++ b/mobile/src/main/res/xml/pref_profile.xml @@ -2,95 +2,88 @@ - + + app:title="@string/proxy_cat"> - - - - + + + + + app:title="@string/feature_cat"> - - + + + app:key="isIpv6" + app:icon="@drawable/ic_image_looks_6" + app:summary="@string/ipv6_summary" + app:title="@string/ipv6"/> + app:key="isProxyApps" + app:icon="@drawable/ic_navigation_apps" + app:summary="@string/proxied_apps_summary" + app:title="@string/proxied_apps"/> + app:key="isUdpDns" + app:icon="@drawable/ic_action_dns" + app:summary="@string/udp_dns_summary" + app:title="@string/udp_dns"/> + app:title="@string/plugin"> - + app:key="plugin" + app:persistent="false" + app:title="@string/plugin" + app:useSimpleSummaryProvider="true"/> + + app:key="udpFallback" + app:title="@string/udp_fallback" + app:summary="@string/plugin_disabled"> diff --git a/plugin/build.gradle b/plugin/build.gradle index cd67827b8b..57b55d0191 100644 --- a/plugin/build.gradle +++ b/plugin/build.gradle @@ -24,8 +24,8 @@ android { } dependencies { - api "androidx.core:core-ktx:1.0.1" - api "com.google.android.material:material:1.1.0-alpha03" + api 'androidx.core:core-ktx:1.0.1' + api 'com.google.android.material:material:1.1.0-alpha03' api "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" testImplementation "junit:junit:$junitVersion" androidTestImplementation "androidx.test:runner:$androidTestVersion" diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt index d5b4ed6482..047e98d272 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt @@ -36,10 +36,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 @@ -53,6 +50,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 @@ -73,9 +71,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) @@ -157,12 +155,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) - (findPreference(Key.isAutoConnect) as SwitchPreference).apply { + findPreference(Key.isAutoConnect).apply { setOnPreferenceChangeListener { _, value -> BootReceiver.enabled = value as Boolean true @@ -170,7 +168,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) { @@ -190,10 +188,13 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo 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(Key.about).apply { summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) setOnPreferenceClickListener { Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show() diff --git a/tv/src/main/res/xml/pref_main.xml b/tv/src/main/res/xml/pref_main.xml index 7f6bb33eb4..ef2219dd84 100644 --- a/tv/src/main/res/xml/pref_main.xml +++ b/tv/src/main/res/xml/pref_main.xml @@ -1,72 +1,61 @@ - + + app:key="profileId" + app:title="@string/connect" + app:persistent="false" + app:useSimpleSummaryProvider="true"/> + app:key="control.stats" + app:title="@string/connection_test_pending" + app:summary="@string/stat_summary"/> + app:key="control.import" + app:title="@string/action_import_file"/> + app:key="control.export" + app:title="@string/action_export_file"/> + app:key="isAutoConnect" + app:persistent="false" + app:summary="@string/auto_connect_summary" + app:title="@string/auto_connect"/> + app:key="tcp_fastopen" + app:summary="@string/tcp_fastopen_summary" + app:title="TCP Fast Open"/> + 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"/> - - - + app:key="shareOverLan" + app:title="@string/share_over_lan"/> + + + + app:key="about" + app:title="@string/about"/> From 046d9fe6f7db10ed56df5a835b8e875462b0f5a9 Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 13 Feb 2019 12:28:37 +0800 Subject: [PATCH 02/14] Refine code style --- .../com/github/shadowsocks/ProfileConfigFragment.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index 4adac29e9d..58a58e4ec7 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -150,12 +150,10 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), override fun onDisplayPreferenceDialog(preference: Preference) { when (preference.key) { - Key.plugin -> { - BottomSheetPreferenceDialogFragment().apply { - setArg(Key.plugin) - setTargetFragment(this@ProfileConfigFragment, 0) - }.show(fragmentManager ?: return, Key.plugin) - } + 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 { From 57b846d22a85a446f5503709d16e822dd7ef8e52 Mon Sep 17 00:00:00 2001 From: Mygod Date: Tue, 12 Feb 2019 20:05:51 +0800 Subject: [PATCH 03/14] Revert "Revert "Revert "Make app fullscreen to match guidelines""" This reverts commit e508915829deeff5d2ee9ad2e64419b1d6339c55. --- .../java/com/github/shadowsocks/tv/MainFragment.kt | 11 ----------- tv/src/main/res/values/styles.xml | 3 +++ 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt index f43b9b057c..4716b5c3e9 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt @@ -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(R.id.settings_preference_fragment_container).updateLayoutParams { - width = ViewGroup.LayoutParams.MATCH_PARENT - } - } } diff --git a/tv/src/main/res/values/styles.xml b/tv/src/main/res/values/styles.xml index d96fdd4613..b7e3e05261 100644 --- a/tv/src/main/res/values/styles.xml +++ b/tv/src/main/res/values/styles.xml @@ -5,6 +5,9 @@ @color/material_accent_200 @color/color_primary @color/color_primary_dark + true + @android:color/transparent + true @style/PreferenceThemeOverlay.v14.Leanback From f41717118c360487f82ddb681c98789032bea654 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 17 Feb 2019 14:03:08 +0800 Subject: [PATCH 04/14] Shrink RAM usage --- .circleci/config.yml | 1 - gradle.properties | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c0f09a255b..ea79ae8c86 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,6 @@ jobs: docker: - image: circleci/android:api-28-ndk environment: - JVM_OPTS: -Xmx3500m GRADLE_OPTS: -Dorg.gradle.workers.max=1 -Dorg.gradle.daemon=false -Dkotlin.compiler.execution.strategy="in-process" steps: - checkout diff --git a/gradle.properties b/gradle.properties index aac8046a69..b96cc2fa3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,4 @@ android.useAndroidX=true # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -org.gradle.jvmargs=-Xmx3500m +org.gradle.jvmargs=-Xmx1536m From ef1ee897f9ff8eae6f55943834ff9e4aa721fbe5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 23 Feb 2019 22:15:48 +0800 Subject: [PATCH 05/14] Fix private issue --- .../main/java/com/github/shadowsocks/ProfileConfigFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index 08544b92af..c4f2431bcd 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -43,7 +43,7 @@ import kotlinx.android.parcel.Parcelize class ProfileConfigFragment : PreferenceFragmentCompat(), Preference.OnPreferenceChangeListener, OnPreferenceDataStoreChangeListener { - private companion object PasswordSummaryProvider : Preference.SummaryProvider { + companion object PasswordSummaryProvider : Preference.SummaryProvider { override fun provideSummary(preference: EditTextPreference?) = "\u2022".repeat(preference?.text?.length ?: 0) private const val REQUEST_CODE_PLUGIN_CONFIGURE = 1 From 5493bcea36773d8fa136e55a41d85a3198110729 Mon Sep 17 00:00:00 2001 From: Mygod Date: Fri, 15 Mar 2019 15:35:53 +0800 Subject: [PATCH 06/14] Update to preference 1.1.0-alpha04 --- core/build.gradle | 2 +- .../shadowsocks/preference/DataStore.kt | 2 +- .../OnPreferenceDataStoreChangeListener.kt | 2 +- .../GlobalSettingsPreferenceFragment.kt | 14 +++++------ .../com/github/shadowsocks/MainActivity.kt | 2 +- .../shadowsocks/ProfileConfigFragment.kt | 18 +++++++------- .../shadowsocks/tv/MainPreferenceFragment.kt | 24 +++++++++---------- 7 files changed, 32 insertions(+), 32 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 8219cd6bc7..709b969b05 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -51,7 +51,7 @@ 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.1.0-alpha03' + 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' diff --git a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt index eb45995a48..8d92b82612 100644 --- a/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt +++ b/core/src/main/java/com/github/shadowsocks/preference/DataStore.kt @@ -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() } diff --git a/core/src/main/java/com/github/shadowsocks/preference/OnPreferenceDataStoreChangeListener.kt b/core/src/main/java/com/github/shadowsocks/preference/OnPreferenceDataStoreChangeListener.kt index bd5c1599ab..e18484b335 100644 --- a/core/src/main/java/com/github/shadowsocks/preference/OnPreferenceDataStoreChangeListener.kt +++ b/core/src/main/java/com/github/shadowsocks/preference/OnPreferenceDataStoreChangeListener.kt @@ -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) } diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index b8ea87af55..f9340420e1 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -39,7 +39,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { preferenceManager.preferenceDataStore = DataStore.publicStore DataStore.initGlobal() addPreferencesFromResource(R.xml.pref_global) - val boot = findPreference(Key.isAutoConnect) + val boot = findPreference(Key.isAutoConnect)!! boot.setOnPreferenceChangeListener { _, value -> BootReceiver.enabled = value as Boolean true @@ -47,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(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) + val tfo = findPreference(Key.tfo)!! tfo.isChecked = DataStore.tcpFastOpen tfo.setOnPreferenceChangeListener { _, value -> if (value as Boolean && !TcpFastOpen.sendEnabled) { @@ -70,12 +70,12 @@ 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 serviceMode = findPreference(Key.serviceMode)!! + val portProxy = findPreference(Key.portProxy)!! portProxy.onBindEditTextListener = PortPreferenceListener - val portLocalDns = findPreference(Key.portLocalDns) + val portLocalDns = findPreference(Key.portLocalDns)!! portLocalDns.onBindEditTextListener = PortPreferenceListener - val portTransproxy = findPreference(Key.portTransproxy) + val portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy.onBindEditTextListener = PortPreferenceListener val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { diff --git a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt index 1e511509cf..5159a556a5 100644 --- a/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt +++ b/mobile/src/main/java/com/github/shadowsocks/MainActivity.kt @@ -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) diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index c4f2431bcd..e4eba5e2d8 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -76,20 +76,20 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), val activity = requireActivity() profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L) addPreferencesFromResource(R.xml.pref_profile) - findPreference(Key.remotePort).onBindEditTextListener = PortPreferenceListener - findPreference(Key.password).summaryProvider = PasswordSummaryProvider + findPreference(Key.remotePort)!!.onBindEditTextListener = PortPreferenceListener + findPreference(Key.password)!!.summaryProvider = PasswordSummaryProvider val serviceMode = DataStore.serviceMode - findPreference(Key.remoteDns).isEnabled = serviceMode != Key.modeProxy - isProxyApps = findPreference(Key.proxyApps) + findPreference(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy + 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) - pluginConfigure = findPreference(Key.pluginConfigure) + findPreference(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) @@ -105,7 +105,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) } @@ -159,7 +159,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), false } - override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String?) { + override fun onPreferenceDataStoreChanged(store: PreferenceDataStore, key: String) { if (key != Key.proxyApps && findPreference(key) != null) DataStore.dirty = true } diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt index b09238e1b6..83381d2e9f 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt @@ -153,12 +153,12 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo preferenceManager.preferenceDataStore = DataStore.publicStore DataStore.initGlobal() addPreferencesFromResource(R.xml.pref_main) - fab = findPreference(Key.id) + fab = findPreference(Key.id)!! populateProfiles() - stats = findPreference(Key.controlStats) - controlImport = findPreference(Key.controlImport) + stats = findPreference(Key.controlStats)!! + controlImport = findPreference(Key.controlImport)!! - findPreference(Key.isAutoConnect).apply { + findPreference(Key.isAutoConnect)!!.apply { setOnPreferenceChangeListener { _, value -> BootReceiver.enabled = value as Boolean true @@ -166,7 +166,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo isChecked = BootReceiver.enabled } - tfo = findPreference(Key.tfo) + tfo = findPreference(Key.tfo)!! tfo.isChecked = DataStore.tcpFastOpen tfo.setOnPreferenceChangeListener { _, value -> if (value as Boolean && !TcpFastOpen.sendEnabled) { @@ -183,16 +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) + serviceMode = findPreference(Key.serviceMode)!! + shareOverLan = findPreference(Key.shareOverLan)!! + portProxy = findPreference(Key.portProxy)!! portProxy.onBindEditTextListener = PortPreferenceListener - portLocalDns = findPreference(Key.portLocalDns) + portLocalDns = findPreference(Key.portLocalDns)!! portLocalDns.onBindEditTextListener = PortPreferenceListener - portTransproxy = findPreference(Key.portTransproxy) + portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy.onBindEditTextListener = PortPreferenceListener serviceMode.onPreferenceChangeListener = onServiceModeChange - findPreference(Key.about).apply { + findPreference(Key.about)!!.apply { summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) setOnPreferenceClickListener { Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show() @@ -236,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()) From b89dccd765dcbcffa02ed60c97f89b49e4769d15 Mon Sep 17 00:00:00 2001 From: ayanamist Date: Fri, 15 Mar 2019 18:10:13 +0800 Subject: [PATCH 07/14] support hosts --- .../github/shadowsocks/net/LocalDnsServer.kt | 70 ++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt b/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt index 2433f2a853..b65fbf8fa2 100644 --- a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt +++ b/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt @@ -22,10 +22,13 @@ package com.github.shadowsocks.net import android.util.Log import com.crashlytics.android.Crashlytics +import com.github.shadowsocks.Core +import com.github.shadowsocks.utils.parseNumericAddress import com.github.shadowsocks.utils.printLog import kotlinx.coroutines.* import org.xbill.DNS.* import java.io.EOFException +import java.io.File import java.io.IOException import java.net.* import java.nio.ByteBuffer @@ -55,6 +58,8 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array = emptyList() + private val hostsMap: Map> = readHosts() + companion object { private const val TAG = "LocalDnsServer" private const val TIMEOUT = 10_000L @@ -65,12 +70,25 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array): ByteBuffer = + ByteBuffer.wrap(prepareDnsResponse(request).apply { + header.setFlag(Flags.RA.toInt()) // recursion available + for (address in results) addRecord(when (address) { + is Inet4Address -> ARecord(question.name, DClass.IN, TTL, address) + is Inet6Address -> AAAARecord(question.name, DClass.IN, TTL, address) + else -> throw IllegalStateException("Unsupported address $address") + }, Section.ANSWER) + }.toWire()) } + private val monitor = ChannelMonitor() private val job = SupervisorJob() @@ -102,10 +120,16 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array Array localResults.any(subnet::matches) }) { remote.cancel() - ByteBuffer.wrap(prepareDnsResponse(request).apply { - header.setFlag(Flags.RA.toInt()) // recursion available - for (address in localResults) addRecord(when (address) { - is Inet4Address -> ARecord(question.name, DClass.IN, TTL, address) - is Inet6Address -> AAAARecord(question.name, DClass.IN, TTL, address) - else -> throw IllegalStateException("Unsupported address $address") - }, Section.ANSWER) - }.toWire()) + prepareDnsResponseWithResults(request, localResults) } else remote.await() } catch (e: Exception) { remote.cancel() @@ -142,6 +159,41 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array = + this.hostsMap[h]?.toTypedArray() ?: emptyArray() + + private fun readHosts(): Map> { + val hostsMap: MutableMap> = HashMap() + try { + val hostsFile = File(Core.deviceStorage.getExternalFilesDir(null), "hosts") + hostsFile.createNewFile() + hostsFile.forEachLine { + val line = it.substringBefore('#') + if (line.isEmpty()) { + return@forEachLine + } + + val splitted = line.trim().split(hostsDelimiter) + if (splitted.size < 2) { + return@forEachLine + } + val addr = splitted[0].parseNumericAddress() ?: return@forEachLine + for (d in splitted.asSequence().drop(1)) { + var el = hostsMap[d] + if (el == null) { + el = HashSet(1) + hostsMap[d] = el + } + el.add(addr) + } + } + return hostsMap.mapValues { it.value.toList() } + } catch (e: IOException) { + printLog(e) + } + return emptyMap() + } + private suspend fun forward(packet: ByteBuffer): ByteBuffer { packet.position(0) // the packet might have been parsed, reset to beginning return if (tcp) SocketChannel.open().use { channel -> From a85c5c200b4b5a203a2d94f276706e3f1a82f525 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 16 Mar 2019 00:47:27 +0800 Subject: [PATCH 08/14] Add hosts to global settings --- .../github/shadowsocks/bg/LocalDnsService.kt | 5 +- .../com/github/shadowsocks/net/HostsFile.kt | 39 ++++++++++++++ .../github/shadowsocks/net/LocalDnsServer.kt | 54 +++---------------- .../preference/HostsSummaryProvider.kt | 33 ++++++++++++ .../com/github/shadowsocks/utils/Constants.kt | 1 + .../com/github/shadowsocks/utils/Utils.kt | 5 +- core/src/main/res/values/strings.xml | 5 +- .../GlobalSettingsPreferenceFragment.kt | 50 +++++++++++++---- ...owsableEditTextPreferenceDialogFragment.kt | 52 ++++++++++++++++++ mobile/src/main/res/xml/pref_global.xml | 5 +- plugin/src/main/res/values/strings.xml | 2 + 11 files changed, 191 insertions(+), 60 deletions(-) create mode 100644 core/src/main/java/com/github/shadowsocks/net/HostsFile.kt create mode 100644 core/src/main/java/com/github/shadowsocks/preference/HostsSummaryProvider.kt create mode 100644 mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt diff --git a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt b/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt index 386c94d42b..73aa5abef6 100644 --- a/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt +++ b/core/src/main/java/com/github/shadowsocks/bg/LocalDnsService.kt @@ -23,10 +23,12 @@ package com.github.shadowsocks.bg import com.github.shadowsocks.Core.app import com.github.shadowsocks.acl.Acl import com.github.shadowsocks.core.R +import com.github.shadowsocks.net.HostsFile import com.github.shadowsocks.net.LocalDnsServer import com.github.shadowsocks.net.Socks5Endpoint import com.github.shadowsocks.net.Subnet import com.github.shadowsocks.preference.DataStore +import com.github.shadowsocks.utils.Key import kotlinx.coroutines.CoroutineScope import java.net.InetSocketAddress import java.net.URI @@ -49,7 +51,8 @@ object LocalDnsService { val dns = URI("dns://${profile.remoteDns}") LocalDnsServer(this::resolver, Socks5Endpoint(dns.host, if (dns.port < 0) 53 else dns.port), - DataStore.proxyAddress).apply { + DataStore.proxyAddress, + HostsFile(DataStore.publicStore.getString(Key.hosts) ?: "")).apply { tcp = !profile.udpdns when (profile.route) { Acl.BYPASS_CHN, Acl.BYPASS_LAN_CHN, Acl.GFWLIST, Acl.CUSTOM_RULES -> { diff --git a/core/src/main/java/com/github/shadowsocks/net/HostsFile.kt b/core/src/main/java/com/github/shadowsocks/net/HostsFile.kt new file mode 100644 index 0000000000..642ad96560 --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/net/HostsFile.kt @@ -0,0 +1,39 @@ +/******************************************************************************* + * * + * Copyright (C) 2019 by Max Lv * + * Copyright (C) 2019 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +package com.github.shadowsocks.net + +import com.github.shadowsocks.utils.computeIfAbsentCompat +import com.github.shadowsocks.utils.parseNumericAddress +import java.net.InetAddress + +class HostsFile(input: String = "") { + private val map = mutableMapOf>() + init { + for (line in input.lineSequence()) { + val entries = line.substringBefore('#').splitToSequence(' ', '\t').filter { it.isNotEmpty() } + val address = entries.firstOrNull()?.parseNumericAddress() ?: continue + for (hostname in entries.drop(1)) map.computeIfAbsentCompat(hostname) { LinkedHashSet(1) }.add(address) + } + } + + val configuredHostnames get() = map.size + fun resolve(hostname: String) = map[hostname]?.shuffled() ?: emptyList() +} diff --git a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt b/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt index b65fbf8fa2..9f760eccd3 100644 --- a/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt +++ b/core/src/main/java/com/github/shadowsocks/net/LocalDnsServer.kt @@ -22,13 +22,10 @@ package com.github.shadowsocks.net import android.util.Log import com.crashlytics.android.Crashlytics -import com.github.shadowsocks.Core -import com.github.shadowsocks.utils.parseNumericAddress import com.github.shadowsocks.utils.printLog import kotlinx.coroutines.* import org.xbill.DNS.* import java.io.EOFException -import java.io.File import java.io.IOException import java.net.* import java.nio.ByteBuffer @@ -46,7 +43,9 @@ import java.nio.channels.SocketChannel * https://github.com/shadowsocks/overture/tree/874f22613c334a3b78e40155a55479b7b69fee04 */ class LocalDnsServer(private val localResolver: suspend (String) -> Array, - private val remoteDns: Socks5Endpoint, private val proxy: SocketAddress) : CoroutineScope { + private val remoteDns: Socks5Endpoint, + private val proxy: SocketAddress, + private val hosts: HostsFile) : CoroutineScope { /** * Forward all requests to remote and ignore localResolver. */ @@ -58,8 +57,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array = emptyList() - private val hostsMap: Map> = readHosts() - companion object { private const val TAG = "LocalDnsServer" private const val TIMEOUT = 10_000L @@ -70,15 +67,13 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array): ByteBuffer = + private fun cookDnsResponse(request: Message, results: Iterable) = ByteBuffer.wrap(prepareDnsResponse(request).apply { header.setFlag(Flags.RA.toInt()) // recursion available for (address in results) addRecord(when (address) { @@ -124,10 +119,10 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array Array localResults.any(subnet::matches) }) { remote.cancel() - prepareDnsResponseWithResults(request, localResults) + cookDnsResponse(request, localResults.asIterable()) } else remote.await() } catch (e: Exception) { remote.cancel() @@ -159,41 +154,6 @@ class LocalDnsServer(private val localResolver: suspend (String) -> Array = - this.hostsMap[h]?.toTypedArray() ?: emptyArray() - - private fun readHosts(): Map> { - val hostsMap: MutableMap> = HashMap() - try { - val hostsFile = File(Core.deviceStorage.getExternalFilesDir(null), "hosts") - hostsFile.createNewFile() - hostsFile.forEachLine { - val line = it.substringBefore('#') - if (line.isEmpty()) { - return@forEachLine - } - - val splitted = line.trim().split(hostsDelimiter) - if (splitted.size < 2) { - return@forEachLine - } - val addr = splitted[0].parseNumericAddress() ?: return@forEachLine - for (d in splitted.asSequence().drop(1)) { - var el = hostsMap[d] - if (el == null) { - el = HashSet(1) - hostsMap[d] = el - } - el.add(addr) - } - } - return hostsMap.mapValues { it.value.toList() } - } catch (e: IOException) { - printLog(e) - } - return emptyMap() - } - private suspend fun forward(packet: ByteBuffer): ByteBuffer { packet.position(0) // the packet might have been parsed, reset to beginning return if (tcp) SocketChannel.open().use { channel -> diff --git a/core/src/main/java/com/github/shadowsocks/preference/HostsSummaryProvider.kt b/core/src/main/java/com/github/shadowsocks/preference/HostsSummaryProvider.kt new file mode 100644 index 0000000000..a3afa4bef7 --- /dev/null +++ b/core/src/main/java/com/github/shadowsocks/preference/HostsSummaryProvider.kt @@ -0,0 +1,33 @@ +/******************************************************************************* + * * + * Copyright (C) 2019 by Max Lv * + * Copyright (C) 2019 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +package com.github.shadowsocks.preference + +import androidx.preference.EditTextPreference +import androidx.preference.Preference +import com.github.shadowsocks.core.R +import com.github.shadowsocks.net.HostsFile + +object HostsSummaryProvider : Preference.SummaryProvider { + override fun provideSummary(preference: EditTextPreference?): CharSequence { + val count = HostsFile(preference!!.text ?: "").configuredHostnames + return preference.context.resources.getQuantityString(R.plurals.hosts_summary, count, count) + } +} diff --git a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt index fb7f9f549e..c2e13323db 100644 --- a/core/src/main/java/com/github/shadowsocks/utils/Constants.kt +++ b/core/src/main/java/com/github/shadowsocks/utils/Constants.kt @@ -65,6 +65,7 @@ object Key { const val dirty = "profileDirty" const val tfo = "tcp_fastopen" + const val hosts = "hosts" const val assetUpdateTime = "assetUpdateTime" // TV specific values diff --git a/core/src/main/java/com/github/shadowsocks/utils/Utils.kt b/core/src/main/java/com/github/shadowsocks/utils/Utils.kt index 3548d58847..5d105112c8 100644 --- a/core/src/main/java/com/github/shadowsocks/utils/Utils.kt +++ b/core/src/main/java/com/github/shadowsocks/utils/Utils.kt @@ -54,9 +54,12 @@ private val parseNumericAddress by lazy { * * Bug: https://issuetracker.google.com/issues/123456213 */ -fun String?.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) +fun String.parseNumericAddress(): InetAddress? = Os.inet_pton(OsConstants.AF_INET, this) ?: Os.inet_pton(OsConstants.AF_INET6, this)?.let { parseNumericAddress.invoke(null, this) as InetAddress } +fun MutableMap.computeIfAbsentCompat(key: K, value: () -> V) = if (Build.VERSION.SDK_INT >= 24) + computeIfAbsent(key) { value() } else this[key] ?: value().also { put(key, it) } + fun HttpURLConnection.disconnectFromMain() { if (Build.VERSION.SDK_INT >= 26) disconnect() else GlobalScope.launch(Dispatchers.IO) { disconnect() } } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 050cbd2e84..c8a890fff5 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -61,6 +61,10 @@ Toggling might require ROOT permission Unsupported kernel version: %s < 3.7.1 Toggle failed + + 1 hostname configured + %d hostnames configured + Send DNS over UDP Requires UDP forwarding on server side UDP Fallback @@ -82,7 +86,6 @@ Please select a profile Proxy/Password should not be empty - Please install a file manager like MiXplorer Connect diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index f9340420e1..eafefb618e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -20,6 +20,8 @@ package com.github.shadowsocks +import android.app.Activity +import android.content.Intent import android.os.Build import android.os.Bundle import androidx.preference.EditTextPreference @@ -31,10 +33,18 @@ 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.BrowsableEditTextPreferenceDialogFragment +import com.github.shadowsocks.preference.HostsSummaryProvider import com.github.shadowsocks.preference.PortPreferenceListener import com.github.shadowsocks.utils.remove class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { + companion object { + private const val REQUEST_BROWSE = 1 + } + + private val hosts by lazy { findPreference(Key.hosts)!! } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.preferenceDataStore = DataStore.publicStore DataStore.initGlobal() @@ -70,6 +80,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) } + hosts.summaryProvider = HostsSummaryProvider val serviceMode = findPreference(Key.serviceMode)!! val portProxy = findPreference(Key.portProxy)!! portProxy.onBindEditTextListener = PortPreferenceListener @@ -84,20 +95,18 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { Key.modeTransproxy -> Pair(true, true) else -> throw IllegalArgumentException("newValue: $newValue") } + hosts.isEnabled = enabledLocalDns portLocalDns.isEnabled = enabledLocalDns portTransproxy.isEnabled = enabledTransproxy true } val listener: (BaseService.State) -> Unit = { - if (it == BaseService.State.Stopped) { - tfo.isEnabled = true - serviceMode.isEnabled = true - portProxy.isEnabled = true - onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode) - } else { - tfo.isEnabled = false - serviceMode.isEnabled = false - portProxy.isEnabled = false + val stopped = it == BaseService.State.Stopped + tfo.isEnabled = stopped + serviceMode.isEnabled = stopped + portProxy.isEnabled = stopped + if (stopped) onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode) else { + hosts.isEnabled = false portLocalDns.isEnabled = false portTransproxy.isEnabled = false } @@ -107,6 +116,29 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { serviceMode.onPreferenceChangeListener = onServiceModeChange } + override fun onDisplayPreferenceDialog(preference: Preference?) { + if (preference == hosts) BrowsableEditTextPreferenceDialogFragment().apply { + setKey(hosts.key) + setTargetFragment(this@GlobalSettingsPreferenceFragment, REQUEST_BROWSE) + }.show(fragmentManager ?: return, hosts.key) else super.onDisplayPreferenceDialog(preference) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + when (requestCode) { + REQUEST_BROWSE -> { + if (resultCode != Activity.RESULT_OK) return + val activity = activity as MainActivity + try { + // we read and persist all its content here to avoid content URL permission issues + hosts.text = activity.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText() + } catch (e: RuntimeException) { + activity.snackbar(e.localizedMessage).show() + } + } + else -> super.onActivityResult(requestCode, resultCode, data) + } + } + override fun onDestroy() { MainActivity.stateListener = null super.onDestroy() diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt new file mode 100644 index 0000000000..6b2c286606 --- /dev/null +++ b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt @@ -0,0 +1,52 @@ +/******************************************************************************* + * * + * Copyright (C) 2019 by Max Lv * + * Copyright (C) 2019 by Mygod Studio * + * * + * 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 . * + * * + *******************************************************************************/ + +package com.github.shadowsocks.preference + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.view.View +import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf +import androidx.preference.EditTextPreferenceDialogFragmentCompat +import com.github.shadowsocks.R +import com.google.android.material.snackbar.Snackbar + +class BrowsableEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { + fun setKey(key: String) { + arguments = bundleOf(Pair(ARG_KEY, key)) + } + + override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) { + super.onPrepareDialogBuilder(builder) + builder.setNeutralButton(R.string.browse) { _, _ -> + val activity = requireActivity() + try { + targetFragment!!.startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "*/*" + }, targetRequestCode) + return@setNeutralButton + } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } + Snackbar.make(activity.findViewById(R.id.content), + R.string.file_manager_missing, Snackbar.LENGTH_SHORT).show() + } + } +} diff --git a/mobile/src/main/res/xml/pref_global.xml b/mobile/src/main/res/xml/pref_global.xml index 953aed77b9..9a7b15fc96 100644 --- a/mobile/src/main/res/xml/pref_global.xml +++ b/mobile/src/main/res/xml/pref_global.xml @@ -1,6 +1,6 @@ + app:initialExpandedChildrenCount="4"> + Yes No Apply + Browse… + Please install a file manager like MiXplorer From 420397fd0e6c839bd615c399c4dc3fc44ecf3584 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 16 Mar 2019 01:05:50 +0800 Subject: [PATCH 09/14] Set hosts to be monospace --- ...ener.kt => EditTextPreferenceModifiers.kt} | 21 +++++++++++++------ .../GlobalSettingsPreferenceFragment.kt | 9 ++++---- .../shadowsocks/ProfileConfigFragment.kt | 3 ++- .../shadowsocks/tv/MainPreferenceFragment.kt | 8 +++---- 4 files changed, 26 insertions(+), 15 deletions(-) rename core/src/main/java/com/github/shadowsocks/preference/{PortPreferenceListener.kt => EditTextPreferenceModifiers.kt} (73%) diff --git a/core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt b/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt similarity index 73% rename from core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt rename to core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt index 8e2a41749e..72ba61c6ae 100644 --- a/core/src/main/java/com/github/shadowsocks/preference/PortPreferenceListener.kt +++ b/core/src/main/java/com/github/shadowsocks/preference/EditTextPreferenceModifiers.kt @@ -20,17 +20,26 @@ package com.github.shadowsocks.preference +import android.graphics.Typeface 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)) +object EditTextPreferenceModifiers { + object Monospace : EditTextPreference.OnBindEditTextListener { + override fun onBindEditText(editText: EditText) { + editText.typeface = Typeface.MONOSPACE + } + } + + object Port : 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() + override fun onBindEditText(editText: EditText) { + editText.inputType = EditorInfo.TYPE_CLASS_NUMBER + editText.filters = portLengthFilter + editText.setSingleLine() + } } } diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index eafefb618e..340123f61a 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -34,8 +34,8 @@ import com.github.shadowsocks.utils.DirectBoot import com.github.shadowsocks.utils.Key import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.preference.BrowsableEditTextPreferenceDialogFragment +import com.github.shadowsocks.preference.EditTextPreferenceModifiers import com.github.shadowsocks.preference.HostsSummaryProvider -import com.github.shadowsocks.preference.PortPreferenceListener import com.github.shadowsocks.utils.remove class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { @@ -80,14 +80,15 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) } + hosts.onBindEditTextListener = EditTextPreferenceModifiers.Monospace hosts.summaryProvider = HostsSummaryProvider val serviceMode = findPreference(Key.serviceMode)!! val portProxy = findPreference(Key.portProxy)!! - portProxy.onBindEditTextListener = PortPreferenceListener + portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port val portLocalDns = findPreference(Key.portLocalDns)!! - portLocalDns.onBindEditTextListener = PortPreferenceListener + portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port val portTransproxy = findPreference(Key.portTransproxy)!! - portTransproxy.onBindEditTextListener = PortPreferenceListener + portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port val onServiceModeChange = Preference.OnPreferenceChangeListener { _, newValue -> val (enabledLocalDns, enabledTransproxy) = when (newValue as String?) { Key.modeProxy -> Pair(false, false) diff --git a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt index 6933fc8c72..eef6b89a0e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/ProfileConfigFragment.kt @@ -74,7 +74,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), val activity = requireActivity() profileId = activity.intent.getLongExtra(Action.EXTRA_PROFILE_ID, -1L) addPreferencesFromResource(R.xml.pref_profile) - findPreference(Key.remotePort)!!.onBindEditTextListener = PortPreferenceListener + findPreference(Key.remotePort)!!.onBindEditTextListener = EditTextPreferenceModifiers.Port findPreference(Key.password)!!.summaryProvider = PasswordSummaryProvider val serviceMode = DataStore.serviceMode findPreference(Key.remoteDns)!!.isEnabled = serviceMode != Key.modeProxy @@ -104,6 +104,7 @@ class ProfileConfigFragment : PreferenceFragmentCompat(), } true } + pluginConfigure.onBindEditTextListener = EditTextPreferenceModifiers.Monospace pluginConfigure.onPreferenceChangeListener = this initPlugins() receiver = Core.listenForPackageChanges(false) { initPlugins() } diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt index 83381d2e9f..b9310869f2 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt @@ -47,8 +47,8 @@ import com.github.shadowsocks.database.ProfileManager import com.github.shadowsocks.net.HttpsTest import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.preference.DataStore +import com.github.shadowsocks.preference.EditTextPreferenceModifiers 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 @@ -186,11 +186,11 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo serviceMode = findPreference(Key.serviceMode)!! shareOverLan = findPreference(Key.shareOverLan)!! portProxy = findPreference(Key.portProxy)!! - portProxy.onBindEditTextListener = PortPreferenceListener + portProxy.onBindEditTextListener = EditTextPreferenceModifiers.Port portLocalDns = findPreference(Key.portLocalDns)!! - portLocalDns.onBindEditTextListener = PortPreferenceListener + portLocalDns.onBindEditTextListener = EditTextPreferenceModifiers.Port portTransproxy = findPreference(Key.portTransproxy)!! - portTransproxy.onBindEditTextListener = PortPreferenceListener + portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port serviceMode.onPreferenceChangeListener = onServiceModeChange findPreference(Key.about)!!.apply { summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) From 83704994ca0efe311cff782ef4a6b4e5405311f5 Mon Sep 17 00:00:00 2001 From: Mygod Date: Sat, 16 Mar 2019 13:15:16 +0800 Subject: [PATCH 10/14] Add hosts to TV --- .../GlobalSettingsPreferenceFragment.kt | 3 +- ...owsableEditTextPreferenceDialogFragment.kt | 6 +- .../shadowsocks/tv/MainPreferenceFragment.kt | 58 ++++++++++++------- tv/src/main/res/xml/pref_main.xml | 5 +- 4 files changed, 45 insertions(+), 27 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt index 340123f61a..ae37045838 100644 --- a/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/GlobalSettingsPreferenceFragment.kt @@ -36,6 +36,7 @@ import com.github.shadowsocks.net.TcpFastOpen import com.github.shadowsocks.preference.BrowsableEditTextPreferenceDialogFragment import com.github.shadowsocks.preference.EditTextPreferenceModifiers import com.github.shadowsocks.preference.HostsSummaryProvider +import com.github.shadowsocks.utils.readableMessage import com.github.shadowsocks.utils.remove class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { @@ -133,7 +134,7 @@ class GlobalSettingsPreferenceFragment : PreferenceFragmentCompat() { // we read and persist all its content here to avoid content URL permission issues hosts.text = activity.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText() } catch (e: RuntimeException) { - activity.snackbar(e.localizedMessage).show() + activity.snackbar(e.readableMessage).show() } } else -> super.onActivityResult(requestCode, resultCode, data) diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt index 6b2c286606..12af378f6b 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt @@ -26,6 +26,7 @@ import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf import androidx.preference.EditTextPreferenceDialogFragmentCompat +import com.github.shadowsocks.MainActivity import com.github.shadowsocks.R import com.google.android.material.snackbar.Snackbar @@ -37,7 +38,7 @@ class BrowsableEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragme override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) { super.onPrepareDialogBuilder(builder) builder.setNeutralButton(R.string.browse) { _, _ -> - val activity = requireActivity() + val activity = activity as MainActivity try { targetFragment!!.startActivityForResult(Intent(Intent.ACTION_GET_CONTENT).apply { addCategory(Intent.CATEGORY_OPENABLE) @@ -45,8 +46,7 @@ class BrowsableEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragme }, targetRequestCode) return@setNeutralButton } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } - Snackbar.make(activity.findViewById(R.id.content), - R.string.file_manager_missing, Snackbar.LENGTH_SHORT).show() + activity.snackbar(activity.getString(R.string.file_manager_missing)).show() } } } diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt index b9310869f2..f5a5949d88 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainPreferenceFragment.kt @@ -48,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.EditTextPreferenceModifiers +import com.github.shadowsocks.preference.HostsSummaryProvider import com.github.shadowsocks.preference.OnPreferenceDataStoreChangeListener import com.github.shadowsocks.utils.Key import com.github.shadowsocks.utils.datas @@ -60,12 +61,14 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo private const val REQUEST_CONNECT = 1 private const val REQUEST_REPLACE_PROFILES = 2 private const val REQUEST_EXPORT_PROFILES = 3 + private const val REQUEST_HOSTS = 4 private const val TAG = "MainPreferenceFragment" } private lateinit var fab: ListPreference private lateinit var stats: Preference private lateinit var controlImport: Preference + private lateinit var hosts: EditTextPreference private lateinit var serviceMode: Preference private lateinit var tfo: SwitchPreference private lateinit var shareOverLan: Preference @@ -79,6 +82,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo Key.modeTransproxy -> Pair(true, true) else -> throw IllegalArgumentException("newValue: $newValue") } + hosts.isEnabled = enabledLocalDns portLocalDns.isEnabled = enabledLocalDns portTransproxy.isEnabled = enabledTransproxy true @@ -118,19 +122,13 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo }) if (msg != null) Toast.makeText(requireContext(), getString(R.string.vpn_error, msg), Toast.LENGTH_SHORT).show() this.state = state - if (state == BaseService.State.Stopped) { - controlImport.isEnabled = true - tfo.isEnabled = true - serviceMode.isEnabled = true - shareOverLan.isEnabled = true - portProxy.isEnabled = true - onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode) - } else { - controlImport.isEnabled = false - tfo.isEnabled = false - serviceMode.isEnabled = false - shareOverLan.isEnabled = false - portProxy.isEnabled = false + val stopped = state == BaseService.State.Stopped + controlImport.isEnabled = stopped + tfo.isEnabled = stopped + serviceMode.isEnabled = stopped + shareOverLan.isEnabled = stopped + portProxy.isEnabled = stopped + if (stopped) onServiceModeChange.onPreferenceChange(null, DataStore.serviceMode) else { portLocalDns.isEnabled = false portTransproxy.isEnabled = false } @@ -183,6 +181,8 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo tfo.summary = getString(R.string.tcp_fastopen_summary_unsupported, System.getProperty("os.version")) } + hosts = findPreference(Key.hosts)!! + hosts.summaryProvider = HostsSummaryProvider serviceMode = findPreference(Key.serviceMode)!! shareOverLan = findPreference(Key.shareOverLan)!! portProxy = findPreference(Key.portProxy)!! @@ -192,13 +192,7 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo portTransproxy = findPreference(Key.portTransproxy)!! portTransproxy.onBindEditTextListener = EditTextPreferenceModifiers.Port serviceMode.onPreferenceChangeListener = onServiceModeChange - findPreference(Key.about)!!.apply { - summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) - setOnPreferenceClickListener { - Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show() - true - } - } + findPreference(Key.about)!!.summary = getString(R.string.about_title, BuildConfig.VERSION_NAME) tester = ViewModelProviders.of(this).get() changeState(BaseService.State.Idle) // reset everything to init state @@ -274,15 +268,25 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo }, REQUEST_EXPORT_PROFILES) true } + Key.about -> { + Toast.makeText(requireContext(), "https://shadowsocks.org/android", Toast.LENGTH_SHORT).show() + true + } else -> super.onPreferenceTreeClick(preference) } - private fun startFilesForResult(intent: Intent, requestCode: Int) { + override fun onDisplayPreferenceDialog(preference: Preference?) { + if (preference != hosts || startFilesForResult(Intent(Intent.ACTION_GET_CONTENT).setType("*/*"), REQUEST_HOSTS)) + super.onDisplayPreferenceDialog(preference) + } + + private fun startFilesForResult(intent: Intent, requestCode: Int): Boolean { try { startActivityForResult(intent.addCategory(Intent.CATEGORY_OPENABLE), requestCode) - return + return false } catch (_: ActivityNotFoundException) { } catch (_: SecurityException) { } Toast.makeText(requireContext(), R.string.file_manager_missing, Toast.LENGTH_SHORT).show() + return true } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -317,6 +321,16 @@ class MainPreferenceFragment : LeanbackPreferenceFragmentCompat(), ShadowsocksCo Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show() } } + REQUEST_HOSTS -> { + if (resultCode != Activity.RESULT_OK) return + val context = requireContext() + try { + // we read and persist all its content here to avoid content URL permission issues + hosts.text = context.contentResolver.openInputStream(data!!.data!!)!!.bufferedReader().readText() + } catch (e: RuntimeException) { + Toast.makeText(context, e.readableMessage, Toast.LENGTH_SHORT).show() + } + } else -> super.onActivityResult(requestCode, resultCode, data) } } diff --git a/tv/src/main/res/xml/pref_main.xml b/tv/src/main/res/xml/pref_main.xml index cc79b34386..e32ff76e5a 100644 --- a/tv/src/main/res/xml/pref_main.xml +++ b/tv/src/main/res/xml/pref_main.xml @@ -19,7 +19,7 @@ app:title="@string/action_export_file"/> + app:initialExpandedChildrenCount="3"> + Date: Sat, 16 Mar 2019 13:15:33 +0800 Subject: [PATCH 11/14] Revert "Revert "Revert "Revert "Make app fullscreen to match guidelines"""" This reverts commit 57b846d22a85a446f5503709d16e822dd7ef8e52. --- .../java/com/github/shadowsocks/tv/MainFragment.kt | 11 +++++++++++ tv/src/main/res/values/styles.xml | 3 --- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt b/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt index 88d8981b65..df10c729dd 100644 --- a/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt +++ b/tv/src/main/java/com/github/shadowsocks/tv/MainFragment.kt @@ -20,7 +20,11 @@ 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.* @@ -55,4 +59,11 @@ class MainFragment : LeanbackSettingsFragmentCompat() { } return super.onPreferenceDisplayDialog(caller, pref) } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + view.findViewById(R.id.settings_preference_fragment_container).updateLayoutParams { + width = ViewGroup.LayoutParams.MATCH_PARENT + } + } } diff --git a/tv/src/main/res/values/styles.xml b/tv/src/main/res/values/styles.xml index b7e3e05261..d96fdd4613 100644 --- a/tv/src/main/res/values/styles.xml +++ b/tv/src/main/res/values/styles.xml @@ -5,9 +5,6 @@ @color/material_accent_200 @color/color_primary @color/color_primary_dark - true - @android:color/transparent - true @style/PreferenceThemeOverlay.v14.Leanback From 638c797e848865e22be5ec342520f870970c82d6 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 18 Mar 2019 17:21:19 +0800 Subject: [PATCH 12/14] Fix unused imports --- .../preference/BrowsableEditTextPreferenceDialogFragment.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt index 12af378f6b..83f480803e 100644 --- a/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt +++ b/mobile/src/main/java/com/github/shadowsocks/preference/BrowsableEditTextPreferenceDialogFragment.kt @@ -22,13 +22,11 @@ package com.github.shadowsocks.preference import android.content.ActivityNotFoundException import android.content.Intent -import android.view.View import androidx.appcompat.app.AlertDialog import androidx.core.os.bundleOf import androidx.preference.EditTextPreferenceDialogFragmentCompat import com.github.shadowsocks.MainActivity import com.github.shadowsocks.R -import com.google.android.material.snackbar.Snackbar class BrowsableEditTextPreferenceDialogFragment : EditTextPreferenceDialogFragmentCompat() { fun setKey(key: String) { From 309b8ce072c6e8adc5a595b57a7b73643637026e Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 May 2019 12:15:31 +0800 Subject: [PATCH 13/14] Update dependencies Fix #2182, #2196. --- core/build.gradle | 2 +- tv/build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index b3470b2a54..658a9da77e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -49,7 +49,7 @@ dependencies { api project(':plugin') api "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion" api "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" - api 'androidx.preference:preference:1.1.0-alpha04' + api 'androidx.preference:preference:1.1.0-alpha05' api "androidx.room:room-runtime:$roomVersion" api 'androidx.work:work-runtime-ktx:2.0.1' api 'com.crashlytics.sdk.android:crashlytics:2.10.0' diff --git a/tv/build.gradle b/tv/build.gradle index 12f1026753..36b8b3382a 100644 --- a/tv/build.gradle +++ b/tv/build.gradle @@ -52,7 +52,7 @@ android { dependencies { implementation project(':core') implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "androidx.leanback:leanback-preference:1.1.0-alpha01" + implementation "androidx.leanback:leanback-preference:1.1.0-alpha02" } apply plugin: 'com.google.gms.google-services' From ea98b94cb529f6827fb8c92aec9d453ee36c90ff Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 8 May 2019 12:27:19 +0800 Subject: [PATCH 14/14] Fix missing super call --- core/src/main/java/com/github/shadowsocks/VpnRequestActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/com/github/shadowsocks/VpnRequestActivity.kt b/core/src/main/java/com/github/shadowsocks/VpnRequestActivity.kt index ac8b301c41..89b4843c31 100644 --- a/core/src/main/java/com/github/shadowsocks/VpnRequestActivity.kt +++ b/core/src/main/java/com/github/shadowsocks/VpnRequestActivity.kt @@ -63,6 +63,7 @@ class VpnRequestActivity : AppCompatActivity() { } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode != REQUEST_CONNECT) return super.onActivityResult(requestCode, resultCode, data) if (resultCode == RESULT_OK) Core.startService() else { Toast.makeText(this, R.string.vpn_permission_denied, Toast.LENGTH_LONG).show() Crashlytics.log(Log.ERROR, TAG, "Failed to start VpnService from onActivityResult: $data")