@@ -3,16 +3,19 @@ package com.thewizrd.common.location
33import android.annotation.SuppressLint
44import android.app.Activity
55import android.content.Context
6- import android.location.Criteria
76import android.location.Location
87import android.location.LocationManager
8+ import android.os.Build
99import android.util.Log
1010import androidx.core.location.LocationManagerCompat
1111import androidx.core.os.CancellationSignal
1212import com.google.android.gms.location.FusedLocationProviderClient
13- import com.google.android.gms.location.LocationRequest
1413import com.google.android.gms.location.LocationServices
14+ import com.google.android.gms.location.Priority
1515import com.google.android.gms.tasks.CancellationTokenSource
16+ import com.google.android.gms.tasks.Task
17+ import com.google.android.gms.tasks.Tasks
18+ import com.google.android.gms.wearable.Wearable
1619import com.thewizrd.common.R
1720import com.thewizrd.common.helpers.locationPermissionEnabled
1821import com.thewizrd.common.utils.ErrorMessage
@@ -22,6 +25,7 @@ import com.thewizrd.shared_resources.exceptions.WeatherException
2225import com.thewizrd.shared_resources.locationdata.LocationData
2326import com.thewizrd.shared_resources.locationdata.toLocation
2427import com.thewizrd.shared_resources.locationdata.toLocationData
28+ import com.thewizrd.shared_resources.utils.ContextUtils.isPhone
2529import com.thewizrd.shared_resources.utils.ConversionMethods
2630import com.thewizrd.shared_resources.utils.Logger
2731import com.thewizrd.weather_api.weatherModule
@@ -65,16 +69,12 @@ class LocationProvider {
6569 suspend fun getLastLocation (): Location ? {
6670 if (! checkPermissions()) return null
6771
68- if (isGMSAvailable) {
72+ val isFusedLocationAvailable = canUseFusedLocation().await()
73+
74+ if (isFusedLocationAvailable) {
6975 return mFusedLocationProviderClient.lastLocation.await()
7076 } else {
71- val locCriteria = Criteria ().apply {
72- accuracy = Criteria .ACCURACY_COARSE
73- isCostAllowed = false
74- powerRequirement = Criteria .POWER_LOW
75- }
76-
77- val provider = mLocationMgr.getBestProvider(locCriteria, true ) ? : return null
77+ val provider = getBestProvider() ? : return null
7878 return mLocationMgr.getLastKnownLocation(provider)
7979 }
8080 }
@@ -89,74 +89,75 @@ class LocationProvider {
8989 return @suspendCancellableCoroutine
9090 }
9191
92- if (isGMSAvailable) {
93- val cts = CancellationTokenSource ()
92+ canUseFusedLocation()
93+ .continueWith { task ->
94+ if (! continuation.isActive) return @continueWith
9495
95- continuation.invokeOnCancellation {
96- cts.cancel()
97- }
96+ val isFusedLocationAvailable = task.result
9897
99- mFusedLocationProviderClient.getCurrentLocation(
100- LocationRequest .PRIORITY_BALANCED_POWER_ACCURACY ,
101- cts.token
102- ).addOnCompleteListener {
103- if (it.isSuccessful) {
104- Logger .writeLine(Log .INFO , " $TAG : Location update received..." )
98+ if (isFusedLocationAvailable) {
99+ val cts = CancellationTokenSource ()
105100
106- if (continuation.isActive) {
107- continuation.resume(it.result)
108- }
109- } else {
110- it.exception?.let { ex ->
111- Logger .writeLine(Log .INFO , ex, " $TAG : Error retrieving location..." )
101+ continuation.invokeOnCancellation {
102+ cts.cancel()
112103 }
113104
114- if (continuation.isActive) {
115- continuation.resume(null )
105+ mFusedLocationProviderClient.getCurrentLocation(
106+ Priority .PRIORITY_BALANCED_POWER_ACCURACY ,
107+ cts.token
108+ ).addOnCompleteListener {
109+ if (it.isSuccessful) {
110+ Logger .writeLine(Log .INFO , " $TAG : Location update received..." )
111+
112+ if (continuation.isActive) {
113+ continuation.resume(it.result)
114+ }
115+ } else {
116+ it.exception?.let { ex ->
117+ Logger .writeLine(Log .INFO , ex, " $TAG : Error retrieving location..." )
118+ }
119+
120+ if (continuation.isActive) {
121+ continuation.resume(null )
122+ }
123+ }
116124 }
117- }
118- }
119- } else {
120- val locCriteria = Criteria ().apply {
121- accuracy = Criteria .ACCURACY_COARSE
122- isCostAllowed = false
123- powerRequirement = Criteria .POWER_LOW
124- }
125-
126- val cancelSignal = CancellationSignal ()
125+ } else {
126+ val cancelSignal = CancellationSignal ()
127127
128- continuation.invokeOnCancellation {
129- cancelSignal.cancel()
130- }
128+ continuation.invokeOnCancellation {
129+ cancelSignal.cancel()
130+ }
131131
132- val provider = mLocationMgr. getBestProvider(locCriteria, true )
132+ val provider = getBestProvider()
133133
134- if (provider == null ) {
135- if (continuation.isActive) {
136- continuation.resume(null )
137- }
138- return @suspendCancellableCoroutine
139- }
134+ if (provider == null ) {
135+ if (continuation.isActive) {
136+ continuation.resume(null )
137+ }
138+ return @continueWith
139+ }
140140
141- runCatching {
142- LocationManagerCompat .getCurrentLocation(
143- mLocationMgr,
144- provider,
145- cancelSignal,
146- Executors .newSingleThreadExecutor()
147- ) {
148- Logger .writeLine(Log .INFO , " $TAG : Location update received..." )
149- if (continuation.isActive) {
150- continuation.resume(it)
141+ runCatching {
142+ LocationManagerCompat .getCurrentLocation(
143+ mLocationMgr,
144+ provider,
145+ cancelSignal,
146+ Executors .newSingleThreadExecutor()
147+ ) {
148+ Logger .writeLine(Log .INFO , " $TAG : Location update received..." )
149+ if (continuation.isActive) {
150+ continuation.resume(it)
151+ }
152+ }
153+ }.onFailure {
154+ Logger .writeLine(Log .INFO , it, " $TAG : Error retrieving location..." )
155+ if (continuation.isActive) {
156+ continuation.resume(null )
157+ }
151158 }
152159 }
153- }.onFailure {
154- Logger .writeLine(Log .INFO , it, " $TAG : Error retrieving location..." )
155- if (continuation.isActive) {
156- continuation.resume(null )
157- }
158160 }
159- }
160161 }
161162
162163 suspend fun getLatestLocationData (previousLocation : LocationData ? = null): LocationResult {
@@ -236,4 +237,80 @@ class LocationProvider {
236237
237238 return LocationResult .NotChanged (previousLocation, false )
238239 }
240+
241+ private fun getBestProvider (): String? {
242+ val enabledProviders = mLocationMgr.getProviders(true )
243+
244+ if (enabledProviders.isNotEmpty()) {
245+ return if (mContext.isPhone() && Build .VERSION .SDK_INT >= Build .VERSION_CODES .S && enabledProviders.contains(
246+ LocationManager .FUSED_PROVIDER
247+ )
248+ ) {
249+ LocationManager .FUSED_PROVIDER
250+ } else if (enabledProviders.contains(LocationManager .GPS_PROVIDER )) {
251+ LocationManager .GPS_PROVIDER
252+ } else if (enabledProviders.contains(LocationManager .NETWORK_PROVIDER )) {
253+ LocationManager .NETWORK_PROVIDER
254+ } else if (enabledProviders.contains(LocationManager .PASSIVE_PROVIDER )) {
255+ LocationManager .PASSIVE_PROVIDER
256+ } else {
257+ enabledProviders.first()
258+ }
259+ }
260+
261+ return null
262+ }
263+
264+ /* *
265+ * On non-wearables: Check if Google Play Services are available. If so, return [com.google.android.gms.location.LocationAvailability.isLocationAvailable]
266+ *
267+ * On wearables: Check if Google Play Services are available. If so, check if any paired phone is available and nearby.
268+ * If so, confirm fused location availability [com.google.android.gms.location.LocationAvailability.isLocationAvailable]
269+ *
270+ * @see [FusedLocationProviderClient.getLocationAvailability]
271+ * @see [com.google.android.gms.wearable.NodeClient.getConnectedNodes]
272+ * @return true if we can use fused location provider
273+ */
274+ private fun canUseFusedLocation (): Task <Boolean > {
275+ return Tasks .forResult(isGMSAvailable)
276+ .continueWithTask {
277+ val isGMSAvailable = it.result
278+
279+ if (isGMSAvailable) {
280+ if (! mContext.isPhone()) {
281+ // Wearables: FusedLocationProvider will likely not be of use if phone is not connected nearby
282+ // Verify phone status
283+ Wearable .getNodeClient(mContext)
284+ .connectedNodes
285+ .continueWithTask { nodesTask ->
286+ if (it.isSuccessful) {
287+ val nodes = nodesTask.result
288+ val isNearbyNodes = nodes.any { n -> n.isNearby }
289+ if (isNearbyNodes) {
290+ isFusedLocationAvailable()
291+ } else {
292+ Tasks .forResult(false )
293+ }
294+ } else {
295+ Tasks .forResult(false )
296+ }
297+ }
298+ } else {
299+ isFusedLocationAvailable()
300+ }
301+ } else {
302+ Tasks .forResult(false )
303+ }
304+ }
305+ }
306+
307+ private fun isFusedLocationAvailable (): Task <Boolean > {
308+ return mFusedLocationProviderClient.locationAvailability.continueWith { avail ->
309+ if (avail.isSuccessful) {
310+ avail.result.isLocationAvailable
311+ } else {
312+ false
313+ }
314+ }
315+ }
239316}
0 commit comments