55
66package org.microg.gms.location.network.wifi
77
8+ import android.annotation.SuppressLint
89import android.content.Context
910import android.location.Location
1011import android.net.ConnectivityManager
1112import android.net.ConnectivityManager.TYPE_WIFI
13+ import android.net.Network
1214import android.os.Build.VERSION.SDK_INT
1315import android.util.Log
16+ import androidx.annotation.RequiresApi
1417import androidx.core.content.getSystemService
1518import androidx.core.location.LocationCompat
1619import kotlinx.coroutines.Dispatchers
@@ -21,6 +24,7 @@ import org.microg.gms.location.network.TAG
2124import java.net.HttpURLConnection
2225import java.net.Proxy
2326import java.net.URL
27+ import java.net.URLConnection
2428import java.security.KeyStore
2529import java.security.cert.*
2630import java.text.SimpleDateFormat
@@ -31,7 +35,6 @@ import javax.net.ssl.TrustManager
3135import javax.net.ssl.TrustManagerFactory
3236import javax.net.ssl.X509TrustManager
3337
34-
3538private val MOVING_WIFI_HOTSPOTS = setOf (
3639 // Austria
3740 " OEBB" ,
@@ -131,79 +134,107 @@ const val KNOTS_TO_METERS_PER_SECOND = 0.5144
131134const val MILES_PER_HOUR_TO_METERS_PER_SECOND = 0.447
132135
133136class 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