Skip to content
This repository was archived by the owner on Nov 17, 2023. It is now read-only.

Commit 04296e7

Browse files
David BritchDavid Britch
authored andcommitted
Android LocationService implementation complete.
1 parent d2bd5a5 commit 04296e7

File tree

8 files changed

+363
-11
lines changed

8 files changed

+363
-11
lines changed

src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@ private async Task GetGpsLocation()
7777
//locationService.AllowsBackgroundUpdates = true;
7878
locator.DesiredAccuracy = 50;
7979

80-
await Task.Delay(5000);
81-
8280
var position = await locator.GetPositionAsync();
8381

8482
_settingsService.Latitude = position.Latitude.ToString();

src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ namespace eShopOnContainers.Core.Services.Location
77
{
88
public interface ILocationServiceImplementation
99
{
10-
event EventHandler<PositionErrorEventArgs> PositionError;
11-
event EventHandler<PositionEventArgs> PositionChanged;
12-
1310
double DesiredAccuracy { get; set; }
1411
bool IsGeolocationAvailable { get; }
1512
bool IsGeolocationEnabled { get; }

src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
using Android.Views;
88
using FFImageLoading;
99
using FFImageLoading.Forms.Droid;
10-
using Plugin.Permissions;
1110
using System;
1211
using Xamarin.Forms.Platform.Android;
12+
using eShopOnContainers.Droid.Services;
1313

1414
namespace eShopOnContainers.Droid.Activities
1515
{
@@ -57,8 +57,7 @@ public override void OnTrimMemory([GeneratedEnum] TrimMemory level)
5757
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
5858
{
5959
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
60-
61-
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
60+
PermissionsService.Instance.OnRequestPermissionResult(requestCode, permissions, grantResults);
6261
}
6362
}
6463
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using eShopOnContainers.Core.Models.Location;
2+
using System;
3+
4+
namespace eShopOnContainers.Droid.Extensions
5+
{
6+
public static class LocationExtensions
7+
{
8+
static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
9+
static int TwoMinutes = 120000;
10+
11+
internal static Position ToPosition(this Android.Locations.Location location)
12+
{
13+
var p = new Position();
14+
if (location.HasAccuracy)
15+
p.Accuracy = location.Accuracy;
16+
if (location.HasAltitude)
17+
p.Altitude = location.Altitude;
18+
if (location.HasBearing)
19+
p.Heading = location.Bearing;
20+
if (location.HasSpeed)
21+
p.Speed = location.Speed;
22+
23+
p.Longitude = location.Longitude;
24+
p.Latitude = location.Latitude;
25+
p.Timestamp = location.GetTimestamp();
26+
return p;
27+
}
28+
29+
internal static DateTimeOffset GetTimestamp(this Android.Locations.Location location)
30+
{
31+
try
32+
{
33+
return new DateTimeOffset(Epoch.AddMilliseconds(location.Time));
34+
}
35+
catch (Exception e)
36+
{
37+
return new DateTimeOffset(Epoch);
38+
}
39+
}
40+
41+
internal static bool IsBetterLocation(this Android.Locations.Location location, Android.Locations.Location bestLocation)
42+
{
43+
44+
if (bestLocation == null)
45+
return true;
46+
47+
var timeDelta = location.Time - bestLocation.Time;
48+
var isSignificantlyNewer = timeDelta > TwoMinutes;
49+
var isSignificantlyOlder = timeDelta < -TwoMinutes;
50+
var isNewer = timeDelta > 0;
51+
52+
if (isSignificantlyNewer)
53+
return true;
54+
55+
if (isSignificantlyOlder)
56+
return false;
57+
58+
var accuracyDelta = (int)(location.Accuracy - bestLocation.Accuracy);
59+
var isLessAccurate = accuracyDelta > 0;
60+
var isMoreAccurate = accuracyDelta < 0;
61+
var isSignificantlyLessAccurage = accuracyDelta > 200;
62+
63+
var isFromSameProvider = IsSameProvider(location.Provider, bestLocation.Provider);
64+
65+
if (isMoreAccurate)
66+
return true;
67+
68+
if (isNewer && !isLessAccurate)
69+
return true;
70+
71+
if (isNewer && !isSignificantlyLessAccurage && isFromSameProvider)
72+
return true;
73+
74+
return false;
75+
}
76+
77+
internal static bool IsSameProvider(string provider1, string provider2)
78+
{
79+
if (provider1 == null)
80+
return provider2 == null;
81+
82+
return provider1.Equals(provider2);
83+
}
84+
}
85+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Android.Locations;
4+
using Android.OS;
5+
using System.Threading;
6+
using System.Collections.Generic;
7+
using Android.Runtime;
8+
using eShopOnContainers.Core.Models.Location;
9+
using eShopOnContainers.Droid.Extensions;
10+
11+
namespace eShopOnContainers.Droid.Services
12+
{
13+
public class GeolocationSingleListener : Java.Lang.Object, ILocationListener
14+
{
15+
readonly object _locationSync = new object();
16+
readonly Action _finishedCallback;
17+
readonly float _desiredAccuracy;
18+
readonly Timer _timer;
19+
readonly TaskCompletionSource<Position> _tcs = new TaskCompletionSource<Position>();
20+
HashSet<string> _activeProviders = new HashSet<string>();
21+
Android.Locations.Location _bestLocation;
22+
23+
public Task<Position> Task => _tcs.Task;
24+
25+
public GeolocationSingleListener(LocationManager manager, float desiredAccuracy, int timeout, IEnumerable<string> activeProviders, Action finishedCallback)
26+
{
27+
_desiredAccuracy = desiredAccuracy;
28+
_finishedCallback = finishedCallback;
29+
30+
_activeProviders = new HashSet<string>(activeProviders);
31+
32+
foreach (var provider in activeProviders)
33+
{
34+
var location = manager.GetLastKnownLocation(provider);
35+
if (location != null && location.IsBetterLocation(_bestLocation))
36+
_bestLocation = location;
37+
}
38+
39+
40+
if (timeout != Timeout.Infinite)
41+
_timer = new Timer(TimesUp, null, timeout, 0);
42+
}
43+
44+
public void Cancel() => _tcs.TrySetCanceled();
45+
46+
public void OnLocationChanged(Android.Locations.Location location)
47+
{
48+
if (location.Accuracy <= _desiredAccuracy)
49+
{
50+
Finish(location);
51+
return;
52+
}
53+
54+
lock (_locationSync)
55+
{
56+
if (location.IsBetterLocation(_bestLocation))
57+
_bestLocation = location;
58+
}
59+
}
60+
61+
public void OnProviderDisabled(string provider)
62+
{
63+
lock (_activeProviders)
64+
{
65+
if (_activeProviders.Remove(provider) && _activeProviders.Count == 0)
66+
_tcs.TrySetException(new GeolocationException(GeolocationError.PositionUnavailable));
67+
}
68+
}
69+
70+
public void OnProviderEnabled(string provider)
71+
{
72+
lock (_activeProviders)
73+
_activeProviders.Add(provider);
74+
}
75+
76+
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
77+
{
78+
switch (status)
79+
{
80+
case Availability.Available:
81+
OnProviderEnabled(provider);
82+
break;
83+
84+
case Availability.OutOfService:
85+
OnProviderDisabled(provider);
86+
break;
87+
}
88+
}
89+
90+
void TimesUp(object state)
91+
{
92+
lock (_locationSync)
93+
{
94+
if (_bestLocation == null)
95+
{
96+
if (_tcs.TrySetCanceled())
97+
_finishedCallback?.Invoke();
98+
}
99+
else
100+
{
101+
Finish(_bestLocation);
102+
}
103+
}
104+
}
105+
106+
void Finish(Android.Locations.Location location)
107+
{
108+
_finishedCallback?.Invoke();
109+
_tcs.TrySetResult(location.ToPosition());
110+
}
111+
}
112+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
using Android.App;
2+
using Android.Content;
3+
using eShopOnContainers.Droid.Services;
4+
using System;
5+
using eShopOnContainers.Core.Services.Location;
6+
using eShopOnContainers.Core.Models.Location;
7+
using eShopOnContainers.Core.Services.Permissions;
8+
using eShopOnContainers.Core.Models.Permissions;
9+
using System.Threading;
10+
using System.Threading.Tasks;
11+
using Android.Locations;
12+
using System.Linq;
13+
using System.Collections.Generic;
14+
using Android.OS;
15+
16+
[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))]
17+
namespace eShopOnContainers.Droid.Services
18+
{
19+
public class LocationServiceImplementation : ILocationServiceImplementation
20+
{
21+
#region Internal Implementation
22+
23+
LocationManager _locationManager;
24+
GeolocationSingleListener _singleListener = null;
25+
26+
string[] Providers => Manager.GetProviders(enabledOnly: false).ToArray();
27+
string[] IgnoredProviders => new string[] { LocationManager.PassiveProvider, "local_database" };
28+
29+
public static string[] ProvidersToUse { get; set; } = new string[] { };
30+
31+
LocationManager Manager
32+
{
33+
get
34+
{
35+
if (_locationManager == null)
36+
_locationManager = (LocationManager)Application.Context.GetSystemService(Context.LocationService);
37+
return _locationManager;
38+
}
39+
}
40+
41+
public LocationServiceImplementation()
42+
{
43+
DesiredAccuracy = 100;
44+
}
45+
46+
async Task<bool> CheckPermissionsAsync()
47+
{
48+
IPermissionsService permissionsService = new PermissionsService();
49+
var status = await permissionsService.CheckPermissionStatusAsync(Permission.Location);
50+
if (status != PermissionStatus.Granted)
51+
{
52+
Console.WriteLine("Currently do not have Location permissions, requesting permissions");
53+
54+
var request = await permissionsService.RequestPermissionsAsync(Permission.Location);
55+
if (request[Permission.Location] != PermissionStatus.Granted)
56+
{
57+
Console.WriteLine("Location permission denied.");
58+
return false;
59+
}
60+
}
61+
62+
return true;
63+
}
64+
65+
#endregion
66+
67+
#region ILocationServiceImplementation
68+
69+
public double DesiredAccuracy { get; set; }
70+
71+
public bool IsGeolocationAvailable => Providers.Length > 0;
72+
73+
public bool IsGeolocationEnabled => Providers.Any(p => !IgnoredProviders.Contains(p) && Manager.IsProviderEnabled(p));
74+
75+
public async Task<Position> GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false)
76+
{
77+
var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite;
78+
if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite)
79+
throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be greater than or equal to 0");
80+
81+
if (!cancelToken.HasValue)
82+
cancelToken = CancellationToken.None;
83+
84+
var hasPermission = await CheckPermissionsAsync();
85+
if (!hasPermission)
86+
throw new GeolocationException(GeolocationError.Unauthorized);
87+
88+
var tcs = new TaskCompletionSource<Position>();
89+
90+
var providers = new List<string>();
91+
if (ProvidersToUse == null || ProvidersToUse.Length == 0)
92+
providers.AddRange(Providers);
93+
else
94+
{
95+
foreach (var provider in Providers)
96+
{
97+
if (ProvidersToUse?.Contains(provider) ?? false)
98+
continue;
99+
providers.Add(provider);
100+
}
101+
}
102+
103+
void SingleListenerFinishCallback()
104+
{
105+
if (_singleListener == null)
106+
return;
107+
108+
for (int i = 0; i < providers.Count; ++i)
109+
Manager.RemoveUpdates(_singleListener);
110+
}
111+
112+
_singleListener = new GeolocationSingleListener(Manager, (float)DesiredAccuracy, timeoutMilliseconds, providers.Where(Manager.IsProviderEnabled), finishedCallback: SingleListenerFinishCallback);
113+
if (cancelToken != CancellationToken.None)
114+
{
115+
cancelToken.Value.Register(() =>
116+
{
117+
_singleListener.Cancel();
118+
119+
for (int i = 0; i < providers.Count; ++i)
120+
Manager.RemoveUpdates(_singleListener);
121+
}, true);
122+
}
123+
124+
try
125+
{
126+
var looper = Looper.MyLooper() ?? Looper.MainLooper;
127+
int enabled = 0;
128+
for (var i = 0; i < providers.Count; ++i)
129+
{
130+
if (Manager.IsProviderEnabled(providers[i]))
131+
enabled++;
132+
133+
Manager.RequestLocationUpdates(providers[i], 0, 0, _singleListener, looper);
134+
}
135+
136+
if (enabled == 0)
137+
{
138+
for (int i = 0; i < providers.Count; ++i)
139+
Manager.RemoveUpdates(_singleListener);
140+
141+
tcs.SetException(new GeolocationException(GeolocationError.PositionUnavailable));
142+
return await tcs.Task;
143+
}
144+
}
145+
catch (Java.Lang.SecurityException ex)
146+
{
147+
tcs.SetException(new GeolocationException(GeolocationError.Unauthorized, ex));
148+
return await tcs.Task;
149+
}
150+
return await _singleListener.Task;
151+
}
152+
153+
#endregion
154+
}
155+
}

0 commit comments

Comments
 (0)