From 8e65c86b9aecbc8fca1b1e0277e46c549e5b182e Mon Sep 17 00:00:00 2001 From: T0astBread Date: Fri, 6 Nov 2020 11:47:44 +0100 Subject: [PATCH 1/9] Implement proxy preferences This commit adds proxy preferences to the settings screen that are then used in the ReverseGeocoder, MapBox and public-transport-enabler (on NetworkProviders that support it, which should apply to every provider). One can verify that the app does not establish a direct internet connection using a tool like nethogs (in Termux). --- .../grobox/transportr/TransportrActivity.kt | 3 +- .../transportr/locations/ReverseGeocoder.kt | 10 ++++- .../transportr/settings/SettingsFragment.kt | 17 ++++++++ .../transportr/settings/SettingsManager.kt | 28 +++++++++++++ .../transportr/utils/TransportrUtils.kt | 18 ++++++++ app/src/main/res/values/arrays.xml | 11 +++++ app/src/main/res/values/strings.xml | 8 ++++ app/src/main/res/xml/preferences.xml | 41 +++++++++++++++++++ 8 files changed, 134 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/de/grobox/transportr/TransportrActivity.kt b/app/src/main/java/de/grobox/transportr/TransportrActivity.kt index a0cec2a20..4017e45eb 100644 --- a/app/src/main/java/de/grobox/transportr/TransportrActivity.kt +++ b/app/src/main/java/de/grobox/transportr/TransportrActivity.kt @@ -30,6 +30,7 @@ import de.grobox.transportr.networks.PickTransportNetworkActivity import de.grobox.transportr.networks.PickTransportNetworkActivity.Companion.FORCE_NETWORK_SELECTION import de.grobox.transportr.networks.TransportNetworkManager import de.grobox.transportr.settings.SettingsManager +import de.grobox.transportr.utils.updateGlobalHttpProxy import java.util.* import javax.inject.Inject @@ -50,6 +51,7 @@ abstract class TransportrActivity : AppCompatActivity() { useLanguage() AppCompatDelegate.setDefaultNightMode(settingsManager.theme) ensureTransportNetworkSelected() + updateGlobalHttpProxy(settingsManager.proxy, manager) super.onCreate(savedInstanceState) } @@ -120,5 +122,4 @@ abstract class TransportrActivity : AppCompatActivity() { finish() } } - } diff --git a/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt b/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt index 5f407f27c..1528de449 100644 --- a/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt +++ b/app/src/main/java/de/grobox/transportr/locations/ReverseGeocoder.kt @@ -23,6 +23,7 @@ import android.content.Context import android.location.Geocoder import androidx.annotation.WorkerThread import com.mapbox.mapboxsdk.geometry.LatLng +import de.grobox.transportr.settings.SettingsManager import de.grobox.transportr.utils.hasLocation import de.schildbach.pte.dto.Location import de.schildbach.pte.dto.LocationType.ADDRESS @@ -31,12 +32,17 @@ import okhttp3.* import org.json.JSONException import org.json.JSONObject import java.io.IOException +import java.net.Proxy import java.util.* +import javax.inject.Inject import kotlin.concurrent.thread class ReverseGeocoder(private val context: Context, private val callback: ReverseGeocoderCallback) { + @Inject + lateinit var settingsManager: SettingsManager + fun findLocation(location: Location) { if (!location.hasLocation()) return findLocation(location.latAsDouble, location.lonAsDouble) @@ -82,7 +88,9 @@ class ReverseGeocoder(private val context: Context, private val callback: Revers } private fun findLocationWithOsm(lat: Double, lon: Double) { - val client = OkHttpClient() + val client = OkHttpClient.Builder() + .proxy(settingsManager.proxy) + .build() // https://nominatim.openstreetmap.org/reverse?lat=52.5217&lon=13.4324&format=json val url = StringBuilder("https://nominatim.openstreetmap.org/reverse?") diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt index 17447399a..622e7a116 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt @@ -36,7 +36,12 @@ import de.grobox.transportr.networks.PickTransportNetworkActivity import de.grobox.transportr.networks.TransportNetwork import de.grobox.transportr.networks.TransportNetworkManager import de.grobox.transportr.settings.SettingsManager.Companion.LANGUAGE +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_ENABLE +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_HOST +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PORT +import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PROTOCOL import de.grobox.transportr.settings.SettingsManager.Companion.THEME +import de.grobox.transportr.utils.updateGlobalHttpProxy import javax.inject.Inject class SettingsFragment : PreferenceFragmentCompat() { @@ -45,6 +50,9 @@ class SettingsFragment : PreferenceFragmentCompat() { val TAG: String = SettingsFragment::class.java.simpleName } + @Inject + internal lateinit var settingsManager: SettingsManager + @Inject internal lateinit var manager: TransportNetworkManager private lateinit var networkPref: Preference @@ -91,6 +99,15 @@ class SettingsFragment : PreferenceFragmentCompat() { true } } + + arrayOf(PROXY_ENABLE, PROXY_HOST, PROXY_PORT, PROXY_PROTOCOL).forEach { prefKey -> + (findPreference(prefKey) as Preference?)?.let { pref -> + pref.setOnPreferenceChangeListener { _, _ -> + updateGlobalHttpProxy(settingsManager.proxy, manager) + true + } + } + } } private fun onTransportNetworkChanged(network: TransportNetwork) { diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt index 56b247ad0..9423bde70 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt @@ -30,6 +30,10 @@ import de.grobox.transportr.R import de.schildbach.pte.NetworkId import de.schildbach.pte.NetworkProvider.Optimize import de.schildbach.pte.NetworkProvider.WalkSpeed +import java.lang.Exception +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Proxy import java.util.* import javax.inject.Inject @@ -92,6 +96,26 @@ class SettingsManager @Inject constructor(private val context: Context) { } } + val proxy: Proxy + get() { + if (!settings.getBoolean(PROXY_ENABLE, false)) + return Proxy.NO_PROXY + return try { + val typeStr = settings.getString(PROXY_PROTOCOL, null) + val type = when (typeStr) { + "SOCKS" -> Proxy.Type.SOCKS + "HTTP" -> Proxy.Type.HTTP + else -> throw IllegalStateException("Illegal proxy type: " + typeStr) + } + val host = settings.getString(PROXY_HOST, null) + val port = Integer.parseInt(settings.getString(PROXY_PORT, null)!!) + Proxy(type, InetSocketAddress(InetAddress.getByName(host), port)) + } catch (e: Exception) { + e.printStackTrace() + Proxy.NO_PROXY + } + } + fun showLocationFragmentOnboarding(): Boolean = settings.getBoolean(LOCATION_ONBOARDING, true) fun locationFragmentOnboardingShown() { settings.edit().putBoolean(LOCATION_ONBOARDING, false).apply() @@ -147,6 +171,10 @@ class SettingsManager @Inject constructor(private val context: Context) { private const val OPTIMIZE = "pref_key_optimize" private const val LOCATION_ONBOARDING = "locationOnboarding" private const val TRIP_DETAIL_ONBOARDING = "tripDetailOnboarding" + internal const val PROXY_ENABLE = "pref_key_proxy_enable" + internal const val PROXY_PROTOCOL = "pref_key_proxy_protocol" + internal const val PROXY_HOST = "pref_key_proxy_host" + internal const val PROXY_PORT = "pref_key_proxy_port" } } diff --git a/app/src/main/java/de/grobox/transportr/utils/TransportrUtils.kt b/app/src/main/java/de/grobox/transportr/utils/TransportrUtils.kt index 58331d6a2..9b7936bda 100644 --- a/app/src/main/java/de/grobox/transportr/utils/TransportrUtils.kt +++ b/app/src/main/java/de/grobox/transportr/utils/TransportrUtils.kt @@ -30,11 +30,16 @@ import android.util.TypedValue import androidx.annotation.AttrRes import androidx.annotation.DrawableRes import androidx.core.content.ContextCompat +import com.mapbox.mapboxsdk.http.HttpRequestUtil import de.grobox.transportr.R +import de.grobox.transportr.networks.TransportNetworkManager +import de.schildbach.pte.AbstractNetworkProvider import de.schildbach.pte.dto.Location import de.schildbach.pte.dto.LocationType import de.schildbach.pte.dto.Product import de.schildbach.pte.dto.Product.* +import okhttp3.OkHttpClient +import java.net.Proxy import java.text.DecimalFormat import kotlin.math.roundToInt @@ -114,3 +119,16 @@ object TransportrUtils { } fun Location.hasLocation() = hasCoord() && (latAs1E6 != 0 || lonAs1E6 != 0) + +fun updateGlobalHttpProxy(newProxy: Proxy, manager: TransportNetworkManager) { + // MapBox + HttpRequestUtil.setOkHttpClient( + OkHttpClient.Builder() + .proxy(newProxy) + .build()) + // public-transport-enabler + manager.transportNetwork.value?.let { + if (it.networkProvider is AbstractNetworkProvider) + (it.networkProvider as AbstractNetworkProvider).setProxy(newProxy) + } +} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index d64e656e3..3dfdf8928 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -98,4 +98,15 @@ NORMAL + + + @string/pref_proxy_protocol_socks + @string/pref_proxy_protocol_http + + + @string/pref_proxy_protocol_value_default + HTTP + + SOCKS + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3b27a49a..e1ab839a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -141,6 +141,14 @@ and always knows where you are to not miss where to get off the bus. Fast Show trip even when screen is locked Show over lock screen + Internet Proxy + Use proxy + Use a proxy for all internet connections + Proxy protocol + SOCKS + HTTP + Proxy server host + Proxy server port Error Internet connection required. Please make sure your internet access is working. diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 8dfa735f1..9ddbdbd4b 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -79,4 +79,45 @@ + + + + + + + + + + + + \ No newline at end of file From 66c83f617858f24e53e6d534b2339fa30dfed00c Mon Sep 17 00:00:00 2001 From: T0ast Date: Mon, 16 Nov 2020 18:54:44 +0100 Subject: [PATCH 2/9] Use Log.e for parsing errors in proxy settings Co-authored-by: Torsten Grote --- .../main/java/de/grobox/transportr/settings/SettingsManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt index 9423bde70..26813d010 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt @@ -111,7 +111,7 @@ class SettingsManager @Inject constructor(private val context: Context) { val port = Integer.parseInt(settings.getString(PROXY_PORT, null)!!) Proxy(type, InetSocketAddress(InetAddress.getByName(host), port)) } catch (e: Exception) { - e.printStackTrace() + Log.e(TAG, "Error parsing proxy settings", e) Proxy.NO_PROXY } } From ce83c525bd510308a7de45ae5bc0a2681456ee80 Mon Sep 17 00:00:00 2001 From: T0astBread Date: Tue, 17 Nov 2020 13:03:28 +0100 Subject: [PATCH 3/9] Add myself to contributors I hope this makes the clabot shut up --- .clabot | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clabot b/.clabot index 657334305..f5a373449 100644 --- a/.clabot +++ b/.clabot @@ -15,7 +15,8 @@ "ajbruin", "hoejmann", "brandsimon", - "sideeffffect" + "sideeffffect", + "T0astBread" ], "label": "cla-signed ✔️", "message": "Thank you for your pull request and welcome to our community! We require contributors to sign our [Contributor License Agreement](https://github.com/grote/Transportr/blob/master/CLA.md), and we don't seem to have the user {{usersWithoutCLA}} on file. In order for your code to get reviewed and merged, please explicitly state that you accept the agreement." From c73843eef545f07528d7d5ff3352ae697338460f Mon Sep 17 00:00:00 2001 From: T0astBread Date: Tue, 17 Nov 2020 13:12:13 +0100 Subject: [PATCH 4/9] Fix broken call to Log in SettingsManager --- .../main/java/de/grobox/transportr/settings/SettingsManager.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt index 26813d010..76624b56a 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt @@ -25,6 +25,7 @@ import android.content.res.Configuration import android.content.res.Resources import android.os.Build import android.preference.PreferenceManager +import android.util.Log import androidx.appcompat.app.AppCompatDelegate.* import de.grobox.transportr.R import de.schildbach.pte.NetworkId @@ -160,6 +161,8 @@ class SettingsManager @Inject constructor(private val context: Context) { } companion object { + private const val TAG = "SettingsManager" + private const val NETWORK_ID_1 = "NetworkId" private const val NETWORK_ID_2 = "NetworkId2" private const val NETWORK_ID_3 = "NetworkId3" From a1ea52d7ab62a3f671ef21b9a18f0e3a0e29060f Mon Sep 17 00:00:00 2001 From: T0astBread Date: Tue, 24 Nov 2020 20:37:35 +0100 Subject: [PATCH 5/9] Use a SwitchPreference for toggling network proxy usage --- app/src/main/res/xml/preferences.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 9ddbdbd4b..c29d37362 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -84,7 +84,7 @@ android:title="@string/pref_proxy" app:iconSpaceReserved="false"> - Date: Tue, 24 Nov 2020 20:38:44 +0100 Subject: [PATCH 6/9] Implement validation for proxy preferences via the newly created ValidatedEditTextPreference --- .../transportr/settings/SettingsFragment.kt | 22 ++++ .../ui/ValidatedEditTextPreference.kt | 112 ++++++++++++++++++ .../dialog_validated_edit_text_preference.xml | 12 ++ app/src/main/res/xml/preferences.xml | 11 +- 4 files changed, 150 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt create mode 100644 app/src/main/res/layout/dialog_validated_edit_text_preference.xml diff --git a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt index 622e7a116..35d607c0b 100644 --- a/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt +++ b/app/src/main/java/de/grobox/transportr/settings/SettingsFragment.kt @@ -29,6 +29,8 @@ import androidx.core.app.ActivityOptionsCompat import androidx.lifecycle.Observer import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import com.google.common.net.HostSpecifier +import com.google.common.net.InternetDomainName import de.grobox.transportr.R import de.grobox.transportr.TransportrApplication import de.grobox.transportr.map.MapActivity @@ -41,6 +43,7 @@ import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_HOST import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PORT import de.grobox.transportr.settings.SettingsManager.Companion.PROXY_PROTOCOL import de.grobox.transportr.settings.SettingsManager.Companion.THEME +import de.grobox.transportr.ui.ValidatedEditTextPreference import de.grobox.transportr.utils.updateGlobalHttpProxy import javax.inject.Inject @@ -108,6 +111,25 @@ class SettingsFragment : PreferenceFragmentCompat() { } } } + + findPreference(PROXY_HOST)!!.validate = { value -> + /* + FIXME: These validation functions are marked unstable, maybe find a replacement + + Valid should be everything accepted by InetAddress.getByName(...), + that is: IPv4 addresses, IPv6 addresses, hostnames and FQDNs. + + Additionally, no network requests should be performed as part of + the validation, so simply using InetAddress.getByName(...) with a + try/catch is out of the question since it might try to resolve + domain names. + */ + InternetDomainName.isValid(value) || HostSpecifier.isValid(value) + } + + findPreference(PROXY_PORT)!!.validate = { value -> + value.toIntOrNull() in 1..65534 + } } private fun onTransportNetworkChanged(network: TransportNetwork) { diff --git a/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt b/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt new file mode 100644 index 000000000..dc9cf531f --- /dev/null +++ b/app/src/main/java/de/grobox/transportr/ui/ValidatedEditTextPreference.kt @@ -0,0 +1,112 @@ +/* + * Transportr + * + * Copyright (c) 2013 - 2020 Torsten Grote + * + * 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 de.grobox.transportr.ui + +import android.content.Context +import android.content.res.TypedArray +import android.text.Editable +import android.text.TextWatcher +import android.util.AttributeSet +import android.view.inputmethod.InputMethodManager +import android.widget.Button +import android.widget.EditText +import android.widget.TextView +import androidx.appcompat.app.AlertDialog +import androidx.preference.Preference +import de.grobox.transportr.R + +/** + * An editable text preference that gives immediate user feedback by + * disabling the "OK" button of the edit dialog if the entered text + * does not match the validation criteria. + * + * Validation can be configured by setting the validate + * property. + */ +class ValidatedEditTextPreference(ctx: Context, attrs: AttributeSet) : + Preference(ctx, attrs, R.attr.preferenceStyle) { + + var validate: ((String) -> Boolean) = { true } + private var persistedValue: String = "" + private var currentValue: String = persistedValue + + init { + setSummaryProvider { persistedValue } + + setOnPreferenceClickListener { + currentValue = persistedValue + + val dialog = AlertDialog.Builder(ctx) + .setTitle(title) + .setView(R.layout.dialog_validated_edit_text_preference) + .setCancelable(true) + .setPositiveButton(R.string.ok) { dialogInterface, _ -> + dialogInterface.dismiss() + persistString(currentValue) + persistedValue = currentValue + notifyChanged() + } + .setNegativeButton(R.string.cancel) { dialogInterface, _ -> + dialogInterface.cancel() + currentValue = persistedValue + } + .create() + dialog.show() + + val editText = dialog.findViewById(R.id.text_input)!! + val okButton = dialog.findViewById