Skip to content

Commit d17a6fa

Browse files
committed
Location/MovingWifi: Try to not bind to network if that fails
This can happen if a VPN is used that prevents bypassing. It's then up to the VPN to ensure those local connections are routed correctly.
1 parent 605ca14 commit d17a6fa

1 file changed

Lines changed: 102 additions & 68 deletions

File tree

  • play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/wifi

play-services-location/core/provider/src/main/kotlin/org/microg/gms/location/network/wifi/MovingWifiHelper.kt

Lines changed: 102 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55

66
package org.microg.gms.location.network.wifi
77

8+
import android.annotation.SuppressLint
89
import android.content.Context
910
import android.location.Location
1011
import android.net.ConnectivityManager
1112
import android.net.ConnectivityManager.TYPE_WIFI
13+
import android.net.Network
1214
import android.os.Build.VERSION.SDK_INT
1315
import android.util.Log
16+
import androidx.annotation.RequiresApi
1417
import androidx.core.content.getSystemService
1518
import androidx.core.location.LocationCompat
1619
import kotlinx.coroutines.Dispatchers
@@ -21,6 +24,7 @@ import org.microg.gms.location.network.TAG
2124
import java.net.HttpURLConnection
2225
import java.net.Proxy
2326
import java.net.URL
27+
import java.net.URLConnection
2428
import java.security.KeyStore
2529
import java.security.cert.*
2630
import java.text.SimpleDateFormat
@@ -31,7 +35,6 @@ import javax.net.ssl.TrustManager
3135
import javax.net.ssl.TrustManagerFactory
3236
import javax.net.ssl.X509TrustManager
3337

34-
3538
private val MOVING_WIFI_HOTSPOTS = setOf(
3639
// Austria
3740
"OEBB",
@@ -131,79 +134,107 @@ const val KNOTS_TO_METERS_PER_SECOND = 0.5144
131134
const val MILES_PER_HOUR_TO_METERS_PER_SECOND = 0.447
132135

133136
class MovingWifiHelper(private val context: Context) {
137+
@RequiresApi(23)
138+
@Suppress("DEPRECATION")
139+
private fun ConnectivityManager.getCurrentWifiNetwork(): Network? = (allNetworks.singleOrNull {
140+
val networkInfo = getNetworkInfo(it)
141+
networkInfo?.type == TYPE_WIFI && networkInfo.isConnected
142+
})
143+
144+
private fun openConnection(network: Network?, url: URL, proxy: Proxy = Proxy.NO_PROXY): URLConnection =
145+
(if (SDK_INT >= 23) network?.openConnection(url, proxy) else null) ?: url.openConnection()
146+
147+
@SuppressLint("CustomX509TrustManager")
148+
private fun disableCertificateRevocationCheck(originalTrustManager: TrustManager): TrustManager {
149+
if (originalTrustManager is X509TrustManager) {
150+
return object : X509TrustManager {
151+
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
152+
Log.d(TAG, "checkClientTrusted: $chain, $authType")
153+
originalTrustManager.checkClientTrusted(chain, authType)
154+
}
155+
156+
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
157+
Log.d(TAG, "checkServerTrusted: $chain, $authType")
158+
originalTrustManager.checkServerTrusted(chain, authType)
159+
}
160+
161+
override fun getAcceptedIssuers(): Array<X509Certificate> {
162+
return originalTrustManager.acceptedIssuers
163+
}
164+
}
165+
} else {
166+
return originalTrustManager
167+
}
168+
}
169+
170+
private fun disableRevocationChecks(connection: HttpsURLConnection) {
171+
try {
172+
val ctx = SSLContext.getInstance("TLS")
173+
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
174+
val ks = KeyStore.getInstance("AndroidCAStore")
175+
ks.load(null, null)
176+
tmf.init(ks)
177+
ctx.init(null, tmf.trustManagers.map(::disableCertificateRevocationCheck).toTypedArray(), null)
178+
connection.sslSocketFactory = ctx.socketFactory
179+
} catch (e: Exception) {
180+
Log.w(TAG, "Failed to disable revocation", e)
181+
}
182+
}
183+
184+
private fun <T> tryConnections(
185+
networks: List<Network?>,
186+
url: URL,
187+
proxy: Proxy = Proxy.NO_PROXY,
188+
use: (connection: HttpURLConnection) -> T
189+
): Pair<T?, List<Exception>> {
190+
val exceptions = mutableListOf<Exception>()
191+
for (network in networks) {
192+
val connection = openConnection(network, url, proxy) as HttpURLConnection
193+
try {
194+
try {
195+
connection.doInput = true
196+
if (connection is HttpsURLConnection && SDK_INT >= 24) disableRevocationChecks(connection)
197+
if (connection.responseCode != 200) throw RuntimeException("Got error")
198+
} catch (e: Exception) {
199+
exceptions.add(e)
200+
continue
201+
}
202+
try {
203+
return use(connection) to exceptions
204+
} catch (e: Exception) {
205+
exceptions.add(e)
206+
break
207+
}
208+
} finally {
209+
try {
210+
connection.inputStream.close()
211+
connection.disconnect()
212+
} catch (ignored: Exception) {
213+
}
214+
}
215+
}
216+
return null to exceptions
217+
}
218+
134219
suspend fun retrieveMovingLocation(current: WifiDetails): Location {
135220
if (!isLocallyRetrievable(current)) throw IllegalArgumentException()
136221
val connectivityManager = context.getSystemService<ConnectivityManager>() ?: throw IllegalStateException()
222+
val network = if (SDK_INT >= 23) connectivityManager.getCurrentWifiNetwork() else null
137223
val sources = MOVING_WIFI_HOTSPOTS_LOCALLY_RETRIEVABLE[current.ssid]!!
138-
val exceptions = mutableListOf<Exception>()
224+
val allExceptions = mutableListOf<Exception>()
139225
for (source in sources) {
140-
try {
141-
val url = URL(source.url)
142-
return withContext(Dispatchers.IO) {
143-
val network = if (isLocallyRetrievable(current) && SDK_INT >= 23) {
144-
@Suppress("DEPRECATION")
145-
(connectivityManager.allNetworks.singleOrNull {
146-
val networkInfo = connectivityManager.getNetworkInfo(it)
147-
networkInfo?.type == TYPE_WIFI && networkInfo.isConnected
148-
})
149-
} else {
150-
null
151-
}
152-
val connection = (if (SDK_INT >= 23) {
153-
network?.openConnection(url, Proxy.NO_PROXY)
154-
} else {
155-
null
156-
} ?: url.openConnection()) as HttpURLConnection
157-
try {
158-
connection.doInput = true
159-
if (connection is HttpsURLConnection && SDK_INT >= 24) {
160-
try {
161-
val ctx = SSLContext.getInstance("TLS")
162-
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
163-
fun wrap(originalTrustManager: TrustManager): TrustManager {
164-
if (originalTrustManager is X509TrustManager) {
165-
return object : X509TrustManager {
166-
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) {
167-
Log.d(TAG, "checkClientTrusted: $chain, $authType")
168-
originalTrustManager.checkClientTrusted(chain, authType)
169-
}
170-
171-
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
172-
Log.d(TAG, "checkServerTrusted: $chain, $authType")
173-
originalTrustManager.checkServerTrusted(chain, authType)
174-
}
175-
176-
override fun getAcceptedIssuers(): Array<X509Certificate> {
177-
return originalTrustManager.acceptedIssuers
178-
}
179-
}
180-
} else {
181-
return originalTrustManager
182-
}
183-
}
184-
val ks = KeyStore.getInstance("AndroidCAStore")
185-
ks.load(null, null)
186-
tmf.init(ks)
187-
ctx.init(null, tmf.trustManagers.map(::wrap).toTypedArray(), null)
188-
connection.sslSocketFactory = ctx.socketFactory
189-
} catch (e: Exception) {
190-
Log.w(TAG, "Failed to disable revocation", e)
191-
}
192-
}
193-
if (connection.responseCode != 200) throw RuntimeException("Got error")
194-
val location = Location(current.ssid ?: "wifi")
195-
source.parse(location, connection.inputStream.readBytes())
196-
} finally {
197-
connection.inputStream.close()
198-
connection.disconnect()
199-
}
226+
val url = URL(source.url)
227+
withContext(Dispatchers.IO) {
228+
val (location, exceptions) = tryConnections(listOfNotNull(network) + null, url) {
229+
val location = Location(current.ssid ?: "wifi")
230+
source.parse(location, it.inputStream.readBytes())
200231
}
201-
} catch (e: Exception) {
202-
exceptions.add(e)
232+
if (location != null) return@withContext location
233+
allExceptions.addAll(exceptions)
203234
}
204235
}
205-
if (exceptions.size == 1) throw exceptions.single()
206-
throw RuntimeException(exceptions.joinToString("\n"))
236+
if (allExceptions.size == 1) throw allExceptions.single()
237+
throw RuntimeException(allExceptions.joinToString("\n"))
207238
}
208239

209240
fun isLocallyRetrievable(wifi: WifiDetails): Boolean =
@@ -301,6 +332,7 @@ class MovingWifiHelper(private val context: Context) {
301332
return location
302333
}
303334
}
335+
304336
private val SOURCE_PASSENGERA_MAV = PassengeraLocationSource("http://portal.mav.hu")
305337
private val SOURCE_PASSENGERA_CD = PassengeraLocationSource("http://cdwifi.cz")
306338

@@ -362,14 +394,15 @@ class MovingWifiHelper(private val context: Context) {
362394
return location
363395
}
364396
}
397+
365398
private val SOURCE_LUFTHANSA_FLYNET_EUROPE = BoardConnectLocationSource("https://www.lufthansa-flynet.com")
366399
private val SOURCE_LUFTHANSA_FLYNET_EUROPE_2 = BoardConnectLocationSource("https://ww2.lufthansa-flynet.com")
367400
private val SOURCE_AUSTRIAN_FLYNET_EUROPE = BoardConnectLocationSource("https://www.austrian-flynet.com")
368401

369402
class SncfLocationSource(base: String) : MovingWifiLocationSource("$base/router/api/train/gps") {
370403
override fun parse(location: Location, data: ByteArray): Location {
371404
val json = JSONObject(data.decodeToString())
372-
if(json.has("fix") && json.getInt("fix") == -1) throw RuntimeException("GPS not valid")
405+
if (json.has("fix") && json.getInt("fix") == -1) throw RuntimeException("GPS not valid")
373406
location.accuracy = 100f
374407
location.latitude = json.getDouble("latitude")
375408
location.longitude = json.getDouble("longitude")
@@ -385,6 +418,7 @@ class MovingWifiHelper(private val context: Context) {
385418
return location
386419
}
387420
}
421+
388422
private val SOURCE_SNCF = SncfLocationSource("https://wifi.sncf")
389423
private val SOURCE_SNCF_INTERCITES = SncfLocationSource("https://wifi.intercites.sncf")
390424
private val SOURCE_NORMANDIE = SncfLocationSource("https://wifi.normandie.fr")
@@ -414,7 +448,7 @@ class MovingWifiHelper(private val context: Context) {
414448
private val SOURCE_OUIFI = object : MovingWifiLocationSource("https://ouifi.ouigo.com:8084/api/gps") {
415449
override fun parse(location: Location, data: ByteArray): Location {
416450
val json = JSONObject(data.decodeToString())
417-
if(json.has("fix") && json.getInt("fix") == -1) throw RuntimeException("GPS not valid")
451+
if (json.has("fix") && json.getInt("fix") == -1) throw RuntimeException("GPS not valid")
418452
location.accuracy = 100f
419453
location.latitude = json.getDouble("latitude")
420454
location.longitude = json.getDouble("longitude")

0 commit comments

Comments
 (0)