Android LocationService implementation complete.
This commit is contained in:
parent
d2bd5a55ec
commit
04296e7a14
@ -77,8 +77,6 @@ namespace eShopOnContainers
|
|||||||
//locationService.AllowsBackgroundUpdates = true;
|
//locationService.AllowsBackgroundUpdates = true;
|
||||||
locator.DesiredAccuracy = 50;
|
locator.DesiredAccuracy = 50;
|
||||||
|
|
||||||
await Task.Delay(5000);
|
|
||||||
|
|
||||||
var position = await locator.GetPositionAsync();
|
var position = await locator.GetPositionAsync();
|
||||||
|
|
||||||
_settingsService.Latitude = position.Latitude.ToString();
|
_settingsService.Latitude = position.Latitude.ToString();
|
||||||
|
@ -7,9 +7,6 @@ namespace eShopOnContainers.Core.Services.Location
|
|||||||
{
|
{
|
||||||
public interface ILocationServiceImplementation
|
public interface ILocationServiceImplementation
|
||||||
{
|
{
|
||||||
event EventHandler<PositionErrorEventArgs> PositionError;
|
|
||||||
event EventHandler<PositionEventArgs> PositionChanged;
|
|
||||||
|
|
||||||
double DesiredAccuracy { get; set; }
|
double DesiredAccuracy { get; set; }
|
||||||
bool IsGeolocationAvailable { get; }
|
bool IsGeolocationAvailable { get; }
|
||||||
bool IsGeolocationEnabled { get; }
|
bool IsGeolocationEnabled { get; }
|
||||||
|
@ -7,9 +7,9 @@ using Android.Runtime;
|
|||||||
using Android.Views;
|
using Android.Views;
|
||||||
using FFImageLoading;
|
using FFImageLoading;
|
||||||
using FFImageLoading.Forms.Droid;
|
using FFImageLoading.Forms.Droid;
|
||||||
using Plugin.Permissions;
|
|
||||||
using System;
|
using System;
|
||||||
using Xamarin.Forms.Platform.Android;
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using eShopOnContainers.Droid.Services;
|
||||||
|
|
||||||
namespace eShopOnContainers.Droid.Activities
|
namespace eShopOnContainers.Droid.Activities
|
||||||
{
|
{
|
||||||
@ -57,8 +57,7 @@ namespace eShopOnContainers.Droid.Activities
|
|||||||
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||||
{
|
{
|
||||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
PermissionsService.Instance.OnRequestPermissionResult(requestCode, permissions, grantResults);
|
||||||
PermissionsImplementation.Current.OnRequestPermissionsResult(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.App;
|
||||||
using Android.Support.V4.Content;
|
using Android.Support.V4.Content;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Android.Icu.Text;
|
|
||||||
|
|
||||||
namespace eShopOnContainers.Droid.Services
|
namespace eShopOnContainers.Droid.Services
|
||||||
{
|
{
|
||||||
public class PermissionService : IPermissionsService
|
public class PermissionsService : IPermissionsService
|
||||||
{
|
{
|
||||||
const int _permissionCode = 25;
|
const int _permissionCode = 25;
|
||||||
object _locker = new object();
|
object _locker = new object();
|
||||||
@ -21,6 +20,8 @@ namespace eShopOnContainers.Droid.Services
|
|||||||
Dictionary<Permission, PermissionStatus> _results;
|
Dictionary<Permission, PermissionStatus> _results;
|
||||||
IList<string> _requestedPermissions;
|
IList<string> _requestedPermissions;
|
||||||
|
|
||||||
|
internal static PermissionsService Instance;
|
||||||
|
|
||||||
#region Internal Implementation
|
#region Internal Implementation
|
||||||
|
|
||||||
List<string> GetManifestNames(Permission permission)
|
List<string> GetManifestNames(Permission permission)
|
||||||
@ -134,6 +135,8 @@ namespace eShopOnContainers.Droid.Services
|
|||||||
|
|
||||||
public Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission)
|
public Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission)
|
||||||
{
|
{
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
var names = GetManifestNames(permission);
|
var names = GetManifestNames(permission);
|
||||||
if (names == null)
|
if (names == null)
|
||||||
{
|
{
|
@ -212,7 +212,10 @@
|
|||||||
<Compile Include="Effects\BaseContainerEffect.cs" />
|
<Compile Include="Effects\BaseContainerEffect.cs" />
|
||||||
<Compile Include="Activities\SplashActivity.cs" />
|
<Compile Include="Activities\SplashActivity.cs" />
|
||||||
<Compile Include="Services\SettingsServiceImplementation.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>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidAsset Include="..\CommonResources\Fonts\Montserrat-Bold.ttf">
|
<AndroidAsset Include="..\CommonResources\Fonts\Montserrat-Bold.ttf">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user