Android LocationService implementation complete.
This commit is contained in:
parent
d2bd5a55ec
commit
04296e7a14
@ -77,8 +77,6 @@ namespace eShopOnContainers
|
||||
//locationService.AllowsBackgroundUpdates = true;
|
||||
locator.DesiredAccuracy = 50;
|
||||
|
||||
await Task.Delay(5000);
|
||||
|
||||
var position = await locator.GetPositionAsync();
|
||||
|
||||
_settingsService.Latitude = position.Latitude.ToString();
|
||||
|
@ -7,9 +7,6 @@ namespace eShopOnContainers.Core.Services.Location
|
||||
{
|
||||
public interface ILocationServiceImplementation
|
||||
{
|
||||
event EventHandler<PositionErrorEventArgs> PositionError;
|
||||
event EventHandler<PositionEventArgs> PositionChanged;
|
||||
|
||||
double DesiredAccuracy { get; set; }
|
||||
bool IsGeolocationAvailable { get; }
|
||||
bool IsGeolocationEnabled { get; }
|
||||
|
@ -7,9 +7,9 @@ using Android.Runtime;
|
||||
using Android.Views;
|
||||
using FFImageLoading;
|
||||
using FFImageLoading.Forms.Droid;
|
||||
using Plugin.Permissions;
|
||||
using System;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using eShopOnContainers.Droid.Services;
|
||||
|
||||
namespace eShopOnContainers.Droid.Activities
|
||||
{
|
||||
@ -57,8 +57,7 @@ namespace eShopOnContainers.Droid.Activities
|
||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
PermissionsService.Instance.OnRequestPermissionResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
using eShopOnContainers.Core.Models.Location;
|
||||
using System;
|
||||
|
||||
namespace eShopOnContainers.Droid.Extensions
|
||||
{
|
||||
public static class LocationExtensions
|
||||
{
|
||||
static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
static int TwoMinutes = 120000;
|
||||
|
||||
internal static Position ToPosition(this Android.Locations.Location location)
|
||||
{
|
||||
var p = new Position();
|
||||
if (location.HasAccuracy)
|
||||
p.Accuracy = location.Accuracy;
|
||||
if (location.HasAltitude)
|
||||
p.Altitude = location.Altitude;
|
||||
if (location.HasBearing)
|
||||
p.Heading = location.Bearing;
|
||||
if (location.HasSpeed)
|
||||
p.Speed = location.Speed;
|
||||
|
||||
p.Longitude = location.Longitude;
|
||||
p.Latitude = location.Latitude;
|
||||
p.Timestamp = location.GetTimestamp();
|
||||
return p;
|
||||
}
|
||||
|
||||
internal static DateTimeOffset GetTimestamp(this Android.Locations.Location location)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DateTimeOffset(Epoch.AddMilliseconds(location.Time));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new DateTimeOffset(Epoch);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsBetterLocation(this Android.Locations.Location location, Android.Locations.Location bestLocation)
|
||||
{
|
||||
|
||||
if (bestLocation == null)
|
||||
return true;
|
||||
|
||||
var timeDelta = location.Time - bestLocation.Time;
|
||||
var isSignificantlyNewer = timeDelta > TwoMinutes;
|
||||
var isSignificantlyOlder = timeDelta < -TwoMinutes;
|
||||
var isNewer = timeDelta > 0;
|
||||
|
||||
if (isSignificantlyNewer)
|
||||
return true;
|
||||
|
||||
if (isSignificantlyOlder)
|
||||
return false;
|
||||
|
||||
var accuracyDelta = (int)(location.Accuracy - bestLocation.Accuracy);
|
||||
var isLessAccurate = accuracyDelta > 0;
|
||||
var isMoreAccurate = accuracyDelta < 0;
|
||||
var isSignificantlyLessAccurage = accuracyDelta > 200;
|
||||
|
||||
var isFromSameProvider = IsSameProvider(location.Provider, bestLocation.Provider);
|
||||
|
||||
if (isMoreAccurate)
|
||||
return true;
|
||||
|
||||
if (isNewer && !isLessAccurate)
|
||||
return true;
|
||||
|
||||
if (isNewer && !isSignificantlyLessAccurage && isFromSameProvider)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool IsSameProvider(string provider1, string provider2)
|
||||
{
|
||||
if (provider1 == null)
|
||||
return provider2 == null;
|
||||
|
||||
return provider1.Equals(provider2);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Locations;
|
||||
using Android.OS;
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Android.Runtime;
|
||||
using eShopOnContainers.Core.Models.Location;
|
||||
using eShopOnContainers.Droid.Extensions;
|
||||
|
||||
namespace eShopOnContainers.Droid.Services
|
||||
{
|
||||
public class GeolocationSingleListener : Java.Lang.Object, ILocationListener
|
||||
{
|
||||
readonly object _locationSync = new object();
|
||||
readonly Action _finishedCallback;
|
||||
readonly float _desiredAccuracy;
|
||||
readonly Timer _timer;
|
||||
readonly TaskCompletionSource<Position> _tcs = new TaskCompletionSource<Position>();
|
||||
HashSet<string> _activeProviders = new HashSet<string>();
|
||||
Android.Locations.Location _bestLocation;
|
||||
|
||||
public Task<Position> Task => _tcs.Task;
|
||||
|
||||
public GeolocationSingleListener(LocationManager manager, float desiredAccuracy, int timeout, IEnumerable<string> activeProviders, Action finishedCallback)
|
||||
{
|
||||
_desiredAccuracy = desiredAccuracy;
|
||||
_finishedCallback = finishedCallback;
|
||||
|
||||
_activeProviders = new HashSet<string>(activeProviders);
|
||||
|
||||
foreach (var provider in activeProviders)
|
||||
{
|
||||
var location = manager.GetLastKnownLocation(provider);
|
||||
if (location != null && location.IsBetterLocation(_bestLocation))
|
||||
_bestLocation = location;
|
||||
}
|
||||
|
||||
|
||||
if (timeout != Timeout.Infinite)
|
||||
_timer = new Timer(TimesUp, null, timeout, 0);
|
||||
}
|
||||
|
||||
public void Cancel() => _tcs.TrySetCanceled();
|
||||
|
||||
public void OnLocationChanged(Android.Locations.Location location)
|
||||
{
|
||||
if (location.Accuracy <= _desiredAccuracy)
|
||||
{
|
||||
Finish(location);
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_locationSync)
|
||||
{
|
||||
if (location.IsBetterLocation(_bestLocation))
|
||||
_bestLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnProviderDisabled(string provider)
|
||||
{
|
||||
lock (_activeProviders)
|
||||
{
|
||||
if (_activeProviders.Remove(provider) && _activeProviders.Count == 0)
|
||||
_tcs.TrySetException(new GeolocationException(GeolocationError.PositionUnavailable));
|
||||
}
|
||||
}
|
||||
|
||||
public void OnProviderEnabled(string provider)
|
||||
{
|
||||
lock (_activeProviders)
|
||||
_activeProviders.Add(provider);
|
||||
}
|
||||
|
||||
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Availability.Available:
|
||||
OnProviderEnabled(provider);
|
||||
break;
|
||||
|
||||
case Availability.OutOfService:
|
||||
OnProviderDisabled(provider);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void TimesUp(object state)
|
||||
{
|
||||
lock (_locationSync)
|
||||
{
|
||||
if (_bestLocation == null)
|
||||
{
|
||||
if (_tcs.TrySetCanceled())
|
||||
_finishedCallback?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
Finish(_bestLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Finish(Android.Locations.Location location)
|
||||
{
|
||||
_finishedCallback?.Invoke();
|
||||
_tcs.TrySetResult(location.ToPosition());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using eShopOnContainers.Droid.Services;
|
||||
using System;
|
||||
using eShopOnContainers.Core.Services.Location;
|
||||
using eShopOnContainers.Core.Models.Location;
|
||||
using eShopOnContainers.Core.Services.Permissions;
|
||||
using eShopOnContainers.Core.Models.Permissions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Locations;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using Android.OS;
|
||||
|
||||
[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))]
|
||||
namespace eShopOnContainers.Droid.Services
|
||||
{
|
||||
public class LocationServiceImplementation : ILocationServiceImplementation
|
||||
{
|
||||
#region Internal Implementation
|
||||
|
||||
LocationManager _locationManager;
|
||||
GeolocationSingleListener _singleListener = null;
|
||||
|
||||
string[] Providers => Manager.GetProviders(enabledOnly: false).ToArray();
|
||||
string[] IgnoredProviders => new string[] { LocationManager.PassiveProvider, "local_database" };
|
||||
|
||||
public static string[] ProvidersToUse { get; set; } = new string[] { };
|
||||
|
||||
LocationManager Manager
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_locationManager == null)
|
||||
_locationManager = (LocationManager)Application.Context.GetSystemService(Context.LocationService);
|
||||
return _locationManager;
|
||||
}
|
||||
}
|
||||
|
||||
public LocationServiceImplementation()
|
||||
{
|
||||
DesiredAccuracy = 100;
|
||||
}
|
||||
|
||||
async Task<bool> CheckPermissionsAsync()
|
||||
{
|
||||
IPermissionsService permissionsService = new PermissionsService();
|
||||
var status = await permissionsService.CheckPermissionStatusAsync(Permission.Location);
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
Console.WriteLine("Currently do not have Location permissions, requesting permissions");
|
||||
|
||||
var request = await permissionsService.RequestPermissionsAsync(Permission.Location);
|
||||
if (request[Permission.Location] != PermissionStatus.Granted)
|
||||
{
|
||||
Console.WriteLine("Location permission denied.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ILocationServiceImplementation
|
||||
|
||||
public double DesiredAccuracy { get; set; }
|
||||
|
||||
public bool IsGeolocationAvailable => Providers.Length > 0;
|
||||
|
||||
public bool IsGeolocationEnabled => Providers.Any(p => !IgnoredProviders.Contains(p) && Manager.IsProviderEnabled(p));
|
||||
|
||||
public async Task<Position> GetPositionAsync(TimeSpan? timeout = null, CancellationToken? cancelToken = null, bool includeHeading = false)
|
||||
{
|
||||
var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite;
|
||||
if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite)
|
||||
throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be greater than or equal to 0");
|
||||
|
||||
if (!cancelToken.HasValue)
|
||||
cancelToken = CancellationToken.None;
|
||||
|
||||
var hasPermission = await CheckPermissionsAsync();
|
||||
if (!hasPermission)
|
||||
throw new GeolocationException(GeolocationError.Unauthorized);
|
||||
|
||||
var tcs = new TaskCompletionSource<Position>();
|
||||
|
||||
var providers = new List<string>();
|
||||
if (ProvidersToUse == null || ProvidersToUse.Length == 0)
|
||||
providers.AddRange(Providers);
|
||||
else
|
||||
{
|
||||
foreach (var provider in Providers)
|
||||
{
|
||||
if (ProvidersToUse?.Contains(provider) ?? false)
|
||||
continue;
|
||||
providers.Add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
void SingleListenerFinishCallback()
|
||||
{
|
||||
if (_singleListener == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < providers.Count; ++i)
|
||||
Manager.RemoveUpdates(_singleListener);
|
||||
}
|
||||
|
||||
_singleListener = new GeolocationSingleListener(Manager, (float)DesiredAccuracy, timeoutMilliseconds, providers.Where(Manager.IsProviderEnabled), finishedCallback: SingleListenerFinishCallback);
|
||||
if (cancelToken != CancellationToken.None)
|
||||
{
|
||||
cancelToken.Value.Register(() =>
|
||||
{
|
||||
_singleListener.Cancel();
|
||||
|
||||
for (int i = 0; i < providers.Count; ++i)
|
||||
Manager.RemoveUpdates(_singleListener);
|
||||
}, true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var looper = Looper.MyLooper() ?? Looper.MainLooper;
|
||||
int enabled = 0;
|
||||
for (var i = 0; i < providers.Count; ++i)
|
||||
{
|
||||
if (Manager.IsProviderEnabled(providers[i]))
|
||||
enabled++;
|
||||
|
||||
Manager.RequestLocationUpdates(providers[i], 0, 0, _singleListener, looper);
|
||||
}
|
||||
|
||||
if (enabled == 0)
|
||||
{
|
||||
for (int i = 0; i < providers.Count; ++i)
|
||||
Manager.RemoveUpdates(_singleListener);
|
||||
|
||||
tcs.SetException(new GeolocationException(GeolocationError.PositionUnavailable));
|
||||
return await tcs.Task;
|
||||
}
|
||||
}
|
||||
catch (Java.Lang.SecurityException ex)
|
||||
{
|
||||
tcs.SetException(new GeolocationException(GeolocationError.Unauthorized, ex));
|
||||
return await tcs.Task;
|
||||
}
|
||||
return await _singleListener.Task;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -9,11 +9,10 @@ using Android.App;
|
||||
using Android.Support.V4.App;
|
||||
using Android.Support.V4.Content;
|
||||
using System.Diagnostics;
|
||||
using Android.Icu.Text;
|
||||
|
||||
namespace eShopOnContainers.Droid.Services
|
||||
{
|
||||
public class PermissionService : IPermissionsService
|
||||
public class PermissionsService : IPermissionsService
|
||||
{
|
||||
const int _permissionCode = 25;
|
||||
object _locker = new object();
|
||||
@ -21,6 +20,8 @@ namespace eShopOnContainers.Droid.Services
|
||||
Dictionary<Permission, PermissionStatus> _results;
|
||||
IList<string> _requestedPermissions;
|
||||
|
||||
internal static PermissionsService Instance;
|
||||
|
||||
#region Internal Implementation
|
||||
|
||||
List<string> GetManifestNames(Permission permission)
|
||||
@ -134,6 +135,8 @@ namespace eShopOnContainers.Droid.Services
|
||||
|
||||
public Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission)
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
var names = GetManifestNames(permission);
|
||||
if (names == null)
|
||||
{
|
@ -212,7 +212,10 @@
|
||||
<Compile Include="Effects\BaseContainerEffect.cs" />
|
||||
<Compile Include="Activities\SplashActivity.cs" />
|
||||
<Compile Include="Services\SettingsServiceImplementation.cs" />
|
||||
<Compile Include="Services\PermissionService.cs" />
|
||||
<Compile Include="Services\PermissionsService.cs" />
|
||||
<Compile Include="Services\LocationServiceImplementation.cs" />
|
||||
<Compile Include="Services\GeolocationSingleListener.cs" />
|
||||
<Compile Include="Extensions\LocationExtensions.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="..\CommonResources\Fonts\Montserrat-Bold.ttf">
|
||||
|
Loading…
x
Reference in New Issue
Block a user