Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement proxy preferences #726

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion .clabot
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/java/de/grobox/transportr/TransportrActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -50,6 +51,7 @@ abstract class TransportrActivity : AppCompatActivity() {
useLanguage()
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
ensureTransportNetworkSelected()
updateGlobalHttpProxy(settingsManager.proxy, manager)

super.onCreate(savedInstanceState)
}
Expand Down Expand Up @@ -120,5 +122,4 @@ abstract class TransportrActivity : AppCompatActivity() {
finish()
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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?")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@ 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
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.ui.ValidatedEditTextPreference
import de.grobox.transportr.utils.updateGlobalHttpProxy
import javax.inject.Inject

class SettingsFragment : PreferenceFragmentCompat() {
Expand All @@ -45,6 +53,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
Expand Down Expand Up @@ -91,6 +102,34 @@ 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
}
}
}

findPreference<ValidatedEditTextPreference>(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)
T0astBread marked this conversation as resolved.
Show resolved Hide resolved
}

findPreference<ValidatedEditTextPreference>(PROXY_PORT)!!.validate = { value ->
value.toIntOrNull() in 1..65534
}
}

private fun onTransportNetworkChanged(network: TransportNetwork) {
Expand Down
31 changes: 31 additions & 0 deletions app/src/main/java/de/grobox/transportr/settings/SettingsManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,16 @@ 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
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

Expand Down Expand Up @@ -92,6 +97,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))
ialokim marked this conversation as resolved.
Show resolved Hide resolved
} catch (e: Exception) {
Log.e(TAG, "Error parsing proxy settings", e)
Proxy.NO_PROXY
}
}

fun showLocationFragmentOnboarding(): Boolean = settings.getBoolean(LOCATION_ONBOARDING, true)
fun locationFragmentOnboardingShown() {
settings.edit().putBoolean(LOCATION_ONBOARDING, false).apply()
Expand Down Expand Up @@ -136,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"
Expand All @@ -147,6 +174,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"
}

}
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

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 <code>validate</code>
* 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<EditText>(R.id.text_input)!!
val okButton = dialog.findViewById<Button>(android.R.id.button1)!!

fun updateUIForValidity() {
okButton.isEnabled = validate(currentValue)
}
updateUIForValidity()

editText.setText(currentValue, TextView.BufferType.NORMAL)
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}

override fun afterTextChanged(text: Editable?) {
currentValue = text.toString()
updateUIForValidity()
}
})
editText.postDelayed({
editText.requestFocus()
editText.setSelection(currentValue.length)
val inputMethodManager = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)
}, 100)

true
}
}

override fun onGetDefaultValue(a: TypedArray?, index: Int): String? {
return a!!.getString(index)
}

override fun onSetInitialValue(defaultValue: Any?) {
super.onSetInitialValue(defaultValue)
persistedValue = getPersistedString(defaultValue?.toString()) ?: persistedValue
currentValue = persistedValue
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/de/grobox/transportr/utils/TransportrUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -114,3 +119,16 @@ object TransportrUtils {
}

fun Location.hasLocation() = hasCoord() && (latAs1E6 != 0 || lonAs1E6 != 0)

fun updateGlobalHttpProxy(newProxy: Proxy, manager: TransportNetworkManager) {
T0astBread marked this conversation as resolved.
Show resolved Hide resolved
// 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)
}
ialokim marked this conversation as resolved.
Show resolved Hide resolved
}
12 changes: 12 additions & 0 deletions app/src/main/res/layout/dialog_validated_edit_text_preference.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="20dp">

<EditText android:id="@+id/text_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

</LinearLayout>
11 changes: 11 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,15 @@
</string-array>
<string name="pref_walk_speed_value_default" translatable="false">NORMAL</string>

<!-- Proxy Protocol -->
<string-array name="pref_proxy_protocol_options">
<item>@string/pref_proxy_protocol_socks</item>
<item>@string/pref_proxy_protocol_http</item>
</string-array>
<string-array name="pref_proxy_protocol_values">
<item>@string/pref_proxy_protocol_value_default</item>
<item>HTTP</item>
</string-array>
<string name="pref_proxy_protocol_value_default" translatable="false">SOCKS</string>

</resources>
8 changes: 8 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ and always knows where you are to not miss where to get off the bus.
<string name="pref_walk_speed_fast">Fast</string>
<string name="pref_show_when_locked_title">Show trip even when screen is locked</string>
<string name="pref_show_when_locked_summary">Show over lock screen</string>
<string name="pref_proxy">Internet Proxy</string>
<string name="pref_proxy_enable_title">Use proxy</string>
<string name="pref_proxy_enable_summary">Use a proxy for all internet connections</string>
<string name="pref_proxy_protocol">Proxy protocol</string>
<string name="pref_proxy_protocol_socks">SOCKS</string>
<string name="pref_proxy_protocol_http">HTTP</string>
<string name="pref_proxy_host">Proxy server host</string>
<string name="pref_proxy_port">Proxy server port</string>

<string name="error">Error</string>
<string name="error_no_internet">Internet connection required. Please make sure your internet access is working.</string>
Expand Down
Loading