Browse Source

Android LocationService implementation complete.

pull/521/head
David Britch 7 years ago
parent
commit
04296e7a14
8 changed files with 363 additions and 11 deletions
  1. +0
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs
  2. +0
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs
  3. +2
    -3
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs
  4. +85
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs
  5. +112
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs
  6. +155
    -0
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs
  7. +5
    -2
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs
  8. +4
    -1
      src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj

+ 0
- 2
src/Mobile/eShopOnContainers/eShopOnContainers.Core/App.xaml.cs View File

@ -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();


+ 0
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.Core/Services/Location/ILocationServiceImplementation.cs View File

@ -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; }


+ 2
- 3
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Activities/MainActivity.cs View File

@ -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);
}
}
}


+ 85
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Extensions/LocationExtensions.cs View File

@ -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);
}
}
}

+ 112
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/GeolocationSingleListener.cs View File

@ -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());
}
}
}

+ 155
- 0
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/LocationServiceImplementation.cs View File

@ -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
}
}

src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionService.cs → src/Mobile/eShopOnContainers/eShopOnContainers.Droid/Services/PermissionsService.cs View File

@ -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)
{

+ 4
- 1
src/Mobile/eShopOnContainers/eShopOnContainers.Droid/eShopOnContainers.Droid.csproj View File

@ -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…
Cancel
Save