@ -0,0 +1,10 @@ | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public enum ActivityType | |||||
{ | |||||
Other, | |||||
AutomotiveNavigation, | |||||
Fitness, | |||||
OtherNavigation | |||||
} | |||||
} |
@ -0,0 +1,38 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class Address | |||||
{ | |||||
public double Latitude { get; set; } | |||||
public double Longitude { get; set; } | |||||
public string CountryCode { get; set; } | |||||
public string CountryName { get; set; } | |||||
public string FeatureName { get; set; } | |||||
public string PostalCode { get; set; } | |||||
public string SubLocality { get; set; } | |||||
public string Thoroughfare { get; set; } | |||||
public string SubThoroughfare { get; set; } | |||||
public string Locality { get; set; } | |||||
public string AdminArea { get; set; } | |||||
public string SubAdminArea { get; set; } | |||||
public Address(Address address) | |||||
{ | |||||
if (address == null) | |||||
throw new ArgumentNullException(nameof(address)); | |||||
CountryCode = address.CountryCode; | |||||
CountryName = address.CountryName; | |||||
Latitude = address.Latitude; | |||||
Longitude = address.Longitude; | |||||
FeatureName = address.FeatureName; | |||||
PostalCode = address.PostalCode; | |||||
SubLocality = address.SubLocality; | |||||
Thoroughfare = address.Thoroughfare; | |||||
SubThoroughfare = address.SubThoroughfare; | |||||
SubAdminArea = address.SubAdminArea; | |||||
AdminArea = address.AdminArea; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,8 @@ | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public enum GeolocationError | |||||
{ | |||||
PositionUnavailable, | |||||
Unauthorized | |||||
} | |||||
} |
@ -0,0 +1,27 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class GeolocationException : Exception | |||||
{ | |||||
public GeolocationError Error { get; private set; } | |||||
public GeolocationException(GeolocationError error) | |||||
: base("A geolocation error occured: " + error) | |||||
{ | |||||
if (!Enum.IsDefined(typeof(GeolocationError), error)) | |||||
throw new ArgumentException("error is not a valid GelocationError member", "error"); | |||||
Error = error; | |||||
} | |||||
public GeolocationException(GeolocationError error, Exception innerException) | |||||
: base("A geolocation error occured: " + error, innerException) | |||||
{ | |||||
if (!Enum.IsDefined(typeof(GeolocationError), error)) | |||||
throw new ArgumentException("error is not a valid GelocationError member", "error"); | |||||
Error = error; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,15 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class ListenerSettings | |||||
{ | |||||
public bool AllowBackgroundUpdates { get; set; } = false; | |||||
public bool PauseLocationUpdatesAutomatically { get; set; } = true; | |||||
public ActivityType ActivityType { get; set; } = ActivityType.Other; | |||||
public bool ListenForSignificantChanges { get; set; } = false; | |||||
public bool DeferLocationUpdates { get; set; } = false; | |||||
public double? DeferralDistanceMeters { get; set; } = 500; | |||||
public TimeSpan? DeferralTime { get; set; } = TimeSpan.FromMinutes(5); | |||||
} | |||||
} |
@ -0,0 +1,43 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class Position | |||||
{ | |||||
public DateTimeOffset Timestamp { get; set; } | |||||
public double Latitude { get; set; } | |||||
public double Longitude { get; set; } | |||||
public double Altitude { get; set; } | |||||
public double Accuracy { get; set; } | |||||
public double AltitudeAccuracy { get; set; } | |||||
public double Heading { get; set; } | |||||
public double Speed { get; set; } | |||||
public Position() | |||||
{ | |||||
} | |||||
public Position(double latitude, double longitude) | |||||
{ | |||||
Timestamp = DateTimeOffset.UtcNow; | |||||
Latitude = latitude; | |||||
Longitude = longitude; | |||||
} | |||||
public Position(Position position) | |||||
{ | |||||
if (position == null) | |||||
throw new ArgumentNullException("position"); | |||||
Timestamp = position.Timestamp; | |||||
Latitude = position.Latitude; | |||||
Longitude = position.Longitude; | |||||
Altitude = position.Altitude; | |||||
AltitudeAccuracy = position.AltitudeAccuracy; | |||||
Accuracy = position.Accuracy; | |||||
Heading = position.Heading; | |||||
Speed = position.Speed; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,14 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class PositionErrorEventArgs : EventArgs | |||||
{ | |||||
public GeolocationError Error { get; private set; } | |||||
public PositionErrorEventArgs(GeolocationError error) | |||||
{ | |||||
Error = error; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,17 @@ | |||||
using System; | |||||
namespace eShopOnContainers.Core.Models.Location | |||||
{ | |||||
public class PositionEventArgs : EventArgs | |||||
{ | |||||
public Position Position { get; private set; } | |||||
public PositionEventArgs(Position position) | |||||
{ | |||||
if (position == null) | |||||
throw new ArgumentNullException("position"); | |||||
Position = position; | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,10 @@ | |||||
namespace eShopOnContainers.Core.Models.Permissions | |||||
{ | |||||
public enum Permission | |||||
{ | |||||
Unknown, | |||||
Location, | |||||
LocationAlways, | |||||
LocationWhenInUse | |||||
} | |||||
} |
@ -0,0 +1,11 @@ | |||||
namespace eShopOnContainers.Core.Models.Permissions | |||||
{ | |||||
public enum PermissionStatus | |||||
{ | |||||
Denied, | |||||
Disabled, | |||||
Granted, | |||||
Restricted, | |||||
Unknown | |||||
} | |||||
} |
@ -0,0 +1,19 @@ | |||||
using System; | |||||
using System.Threading; | |||||
using System.Threading.Tasks; | |||||
using eShopOnContainers.Core.Models.Location; | |||||
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; } | |||||
Task<Position> GetPositionAsync(TimeSpan? timeout = null, CancellationToken? token = null, bool includeHeading = false); | |||||
} | |||||
} |
@ -0,0 +1,12 @@ | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | |||||
using eShopOnContainers.Core.Models.Permissions; | |||||
namespace eShopOnContainers.Core.Services.Permissions | |||||
{ | |||||
public interface IPermissionsService | |||||
{ | |||||
Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission); | |||||
Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions); | |||||
} | |||||
} |
@ -0,0 +1,241 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | |||||
using eShopOnContainers.Core.Models.Permissions; | |||||
using eShopOnContainers.Core.Services.Permissions; | |||||
using System.Linq; | |||||
using Android; | |||||
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 | |||||
{ | |||||
const int _permissionCode = 25; | |||||
object _locker = new object(); | |||||
TaskCompletionSource<Dictionary<Permission, PermissionStatus>> _tcs; | |||||
Dictionary<Permission, PermissionStatus> _results; | |||||
IList<string> _requestedPermissions; | |||||
#region Internal Implementation | |||||
List<string> GetManifestNames(Permission permission) | |||||
{ | |||||
var permissionNames = new List<string>(); | |||||
switch (permission) | |||||
{ | |||||
case Permission.LocationAlways: | |||||
case Permission.LocationWhenInUse: | |||||
case Permission.Location: | |||||
{ | |||||
if (HasPermissionInManifest(Manifest.Permission.AccessCoarseLocation)) | |||||
permissionNames.Add(Manifest.Permission.AccessCoarseLocation); | |||||
if (HasPermissionInManifest(Manifest.Permission.AccessFineLocation)) | |||||
permissionNames.Add(Manifest.Permission.AccessFineLocation); | |||||
} | |||||
break; | |||||
} | |||||
return permissionNames; | |||||
} | |||||
bool HasPermissionInManifest(string permission) | |||||
{ | |||||
try | |||||
{ | |||||
if (_requestedPermissions != null) | |||||
return _requestedPermissions.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)); | |||||
// Try to use current activity else application context | |||||
var context = MainApplication.CurrentContext ?? Application.Context; | |||||
if (context == null) | |||||
{ | |||||
Debug.WriteLine("Unable to detect current Activity or Application Context."); | |||||
return false; | |||||
} | |||||
var info = context.PackageManager.GetPackageInfo(context.PackageName, Android.Content.PM.PackageInfoFlags.Permissions); | |||||
if (info == null) | |||||
{ | |||||
Debug.WriteLine("Unable to get package info, will not be able to determine permissions to request."); | |||||
return false; | |||||
} | |||||
_requestedPermissions = info.RequestedPermissions; | |||||
if (_requestedPermissions == null) | |||||
{ | |||||
Debug.WriteLine("There are no requested permissions, please check to ensure you have marked the permissions that you want to request."); | |||||
return false; | |||||
} | |||||
return _requestedPermissions.Any(r => r.Equals(permission, StringComparison.InvariantCultureIgnoreCase)); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
Console.Write("Unable to check manifest for permission: " + ex); | |||||
} | |||||
return false; | |||||
} | |||||
static Permission GetPermissionForManifestName(string permission) | |||||
{ | |||||
switch (permission) | |||||
{ | |||||
case Manifest.Permission.AccessCoarseLocation: | |||||
case Manifest.Permission.AccessFineLocation: | |||||
return Permission.Location; | |||||
} | |||||
return Permission.Unknown; | |||||
} | |||||
public void OnRequestPermissionResult(int requestCode, string[] permissions, Android.Content.PM.Permission[] grantResults) | |||||
{ | |||||
if (requestCode != _permissionCode) | |||||
return; | |||||
if (_tcs == null) | |||||
return; | |||||
for (var i = 0; i < permissions.Length; i++) | |||||
{ | |||||
if (_tcs.Task.Status == TaskStatus.Canceled) | |||||
return; | |||||
var permission = GetPermissionForManifestName(permissions[i]); | |||||
if (permission == Permission.Unknown) | |||||
continue; | |||||
lock (_locker) | |||||
{ | |||||
if (permission == Permission.Location) | |||||
{ | |||||
if (!_results.ContainsKey(Permission.LocationWhenInUse)) | |||||
_results.Add(Permission.LocationWhenInUse, grantResults[i] == Android.Content.PM.Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); | |||||
} | |||||
if (!_results.ContainsKey(permission)) | |||||
_results.Add(permission, grantResults[i] == Android.Content.PM.Permission.Granted ? PermissionStatus.Granted : PermissionStatus.Denied); | |||||
} | |||||
} | |||||
_tcs.TrySetResult(_results); | |||||
} | |||||
#endregion | |||||
#region IPermissionsService Implementation | |||||
public Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission) | |||||
{ | |||||
var names = GetManifestNames(permission); | |||||
if (names == null) | |||||
{ | |||||
Debug.WriteLine("No Android specific permissions needed for: " + permission); | |||||
return Task.FromResult(PermissionStatus.Granted); | |||||
} | |||||
if (names.Count == 0) | |||||
{ | |||||
Debug.WriteLine("No permissions found in manifest for: " + permission); | |||||
return Task.FromResult(PermissionStatus.Unknown); | |||||
} | |||||
var context = MainApplication.CurrentContext ?? Application.Context; | |||||
if (context == null) | |||||
{ | |||||
Debug.WriteLine("Unable to detect current Activity or Application Context."); | |||||
return Task.FromResult(PermissionStatus.Unknown); | |||||
} | |||||
bool targetsMOrHigher = context.ApplicationInfo.TargetSdkVersion >= Android.OS.BuildVersionCodes.M; | |||||
foreach (var name in names) | |||||
{ | |||||
if (targetsMOrHigher) | |||||
{ | |||||
if (ContextCompat.CheckSelfPermission(context, name) != Android.Content.PM.Permission.Granted) | |||||
return Task.FromResult(PermissionStatus.Denied); | |||||
} | |||||
else | |||||
{ | |||||
if (PermissionChecker.CheckSelfPermission(context, name) != PermissionChecker.PermissionGranted) | |||||
return Task.FromResult(PermissionStatus.Denied); | |||||
} | |||||
} | |||||
return Task.FromResult(PermissionStatus.Granted); | |||||
} | |||||
public async Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions) | |||||
{ | |||||
if (_tcs != null && !_tcs.Task.IsCompleted) | |||||
{ | |||||
_tcs.SetCanceled(); | |||||
_tcs = null; | |||||
} | |||||
lock (_locker) | |||||
{ | |||||
_results = new Dictionary<Permission, PermissionStatus>(); | |||||
} | |||||
var context = MainApplication.CurrentContext; | |||||
if (context == null) | |||||
{ | |||||
Debug.WriteLine("Unable to detect current Activity."); | |||||
foreach (var permission in permissions) | |||||
{ | |||||
lock (_locker) | |||||
{ | |||||
if (!_results.ContainsKey(permission)) | |||||
_results.Add(permission, PermissionStatus.Unknown); | |||||
} | |||||
} | |||||
return _results; | |||||
} | |||||
var permissionsToRequest = new List<string>(); | |||||
foreach (var permission in permissions) | |||||
{ | |||||
var result = await CheckPermissionStatusAsync(permission).ConfigureAwait(false); | |||||
if (result != PermissionStatus.Granted) | |||||
{ | |||||
var names = GetManifestNames(permission); | |||||
if ((names?.Count ?? 0) == 0) | |||||
{ | |||||
lock (_locker) | |||||
{ | |||||
if (!_results.ContainsKey(permission)) | |||||
_results.Add(permission, PermissionStatus.Unknown); | |||||
} | |||||
continue; | |||||
} | |||||
permissionsToRequest.AddRange(names); | |||||
} | |||||
else | |||||
{ | |||||
lock (_locker) | |||||
{ | |||||
if (!_results.ContainsKey(permission)) | |||||
_results.Add(permission, PermissionStatus.Granted); | |||||
} | |||||
} | |||||
} | |||||
if (permissionsToRequest.Count == 0) | |||||
return _results; | |||||
_tcs = new TaskCompletionSource<Dictionary<Permission, PermissionStatus>>(); | |||||
ActivityCompat.RequestPermissions((Activity)context, permissionsToRequest.ToArray(), _permissionCode); | |||||
return await _tcs.Task.ConfigureAwait(false); | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@ -0,0 +1,139 @@ | |||||
using System; | |||||
using CoreLocation; | |||||
using Foundation; | |||||
using System.Threading.Tasks; | |||||
using System.Threading; | |||||
using System.Linq; | |||||
using eShopOnContainers.Core.Models.Location; | |||||
namespace eShopOnContainers.iOS.Services | |||||
{ | |||||
internal class GeolocationSingleUpdateDelegate : CLLocationManagerDelegate | |||||
{ | |||||
bool _haveHeading; | |||||
bool _haveLocation; | |||||
readonly Position _position = new Position(); | |||||
CLHeading _bestHeading; | |||||
readonly double _desiredAccuracy; | |||||
readonly bool _includeHeading; | |||||
readonly TaskCompletionSource<Position> _tcs; | |||||
readonly CLLocationManager _manager; | |||||
public Task<Position> Task => _tcs?.Task; | |||||
public GeolocationSingleUpdateDelegate(CLLocationManager manager, double desiredAccuracy, bool includeHeading, int timeout, CancellationToken cancelToken) | |||||
{ | |||||
_manager = manager; | |||||
_tcs = new TaskCompletionSource<Position>(manager); | |||||
_desiredAccuracy = desiredAccuracy; | |||||
_includeHeading = includeHeading; | |||||
if (timeout != Timeout.Infinite) | |||||
{ | |||||
Timer t = null; | |||||
t = new Timer(s => | |||||
{ | |||||
if (_haveLocation) | |||||
_tcs.TrySetResult(new Position(_position)); | |||||
else | |||||
_tcs.TrySetCanceled(); | |||||
StopListening(); | |||||
t.Dispose(); | |||||
}, null, timeout, 0); | |||||
} | |||||
cancelToken.Register(() => | |||||
{ | |||||
StopListening(); | |||||
_tcs.TrySetCanceled(); | |||||
}); | |||||
} | |||||
public override void AuthorizationChanged(CLLocationManager manager, CLAuthorizationStatus status) | |||||
{ | |||||
// If user has services disabled, throw an exception for consistency. | |||||
if (status == CLAuthorizationStatus.Denied || status == CLAuthorizationStatus.Restricted) | |||||
{ | |||||
StopListening(); | |||||
_tcs.TrySetException(new GeolocationException(GeolocationError.Unauthorized)); | |||||
} | |||||
} | |||||
public override void Failed(CLLocationManager manager, NSError error) | |||||
{ | |||||
switch ((CLError)(int)error.Code) | |||||
{ | |||||
case CLError.Network: | |||||
StopListening(); | |||||
_tcs.SetException(new GeolocationException(GeolocationError.PositionUnavailable)); | |||||
break; | |||||
case CLError.LocationUnknown: | |||||
StopListening(); | |||||
_tcs.TrySetException(new GeolocationException(GeolocationError.PositionUnavailable)); | |||||
break; | |||||
} | |||||
} | |||||
public override bool ShouldDisplayHeadingCalibration(CLLocationManager manager) => true; | |||||
public override void UpdatedLocation(CLLocationManager manager, CLLocation newLocation, CLLocation oldLocation) | |||||
{ | |||||
if (newLocation.HorizontalAccuracy < 0) | |||||
return; | |||||
if (_haveLocation && newLocation.HorizontalAccuracy > _position.Accuracy) | |||||
return; | |||||
_position.Accuracy = newLocation.HorizontalAccuracy; | |||||
_position.Altitude = newLocation.Altitude; | |||||
_position.AltitudeAccuracy = newLocation.VerticalAccuracy; | |||||
_position.Latitude = newLocation.Coordinate.Latitude; | |||||
_position.Longitude = newLocation.Coordinate.Longitude; | |||||
_position.Speed = newLocation.Speed; | |||||
try | |||||
{ | |||||
_position.Timestamp = new DateTimeOffset((DateTime)newLocation.Timestamp); | |||||
} | |||||
catch (Exception ex) | |||||
{ | |||||
_position.Timestamp = DateTimeOffset.UtcNow; | |||||
} | |||||
_haveLocation = true; | |||||
if ((!_includeHeading || _haveHeading) && _position.Accuracy <= _desiredAccuracy) | |||||
{ | |||||
_tcs.TrySetResult(new Position(_position)); | |||||
StopListening(); | |||||
} | |||||
} | |||||
public override void UpdatedHeading(CLLocationManager manager, CLHeading newHeading) | |||||
{ | |||||
if (newHeading.HeadingAccuracy < 0) | |||||
return; | |||||
if (_bestHeading != null && newHeading.HeadingAccuracy >= _bestHeading.HeadingAccuracy) | |||||
return; | |||||
_bestHeading = newHeading; | |||||
_position.Heading = newHeading.TrueHeading; | |||||
_haveHeading = true; | |||||
if (_haveLocation && _position.Accuracy <= _desiredAccuracy) | |||||
{ | |||||
_tcs.TrySetResult(new Position(_position)); | |||||
StopListening(); | |||||
} | |||||
} | |||||
private void StopListening() | |||||
{ | |||||
if (CLLocationManager.HeadingAvailable) | |||||
_manager.StopUpdatingHeading(); | |||||
_manager.StopUpdatingLocation(); | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,150 @@ | |||||
using eShopOnContainers.iOS.Services; | |||||
using eShopOnContainers.Core.Services.Location; | |||||
using CoreLocation; | |||||
using eShopOnContainers.Core.Models.Location; | |||||
using Foundation; | |||||
using System; | |||||
using System.Threading.Tasks; | |||||
using System.Threading; | |||||
using UIKit; | |||||
using eShopOnContainers.Core.Models.Permissions; | |||||
using eShopOnContainers.Core.Services.Permissions; | |||||
[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))] | |||||
namespace eShopOnContainers.iOS.Services | |||||
{ | |||||
public class LocationServiceImplementation : ILocationServiceImplementation | |||||
{ | |||||
bool _deferringUpdates; | |||||
readonly CLLocationManager _manager; | |||||
Position _lastPosition; | |||||
public event EventHandler<PositionErrorEventArgs> PositionError; | |||||
public event EventHandler<PositionEventArgs> PositionChanged; | |||||
public double DesiredAccuracy { get; set; } | |||||
public bool IsGeolocationAvailable => true; | |||||
public bool IsGeolocationEnabled | |||||
{ | |||||
get | |||||
{ | |||||
var status = CLLocationManager.Status; | |||||
return CLLocationManager.LocationServicesEnabled; | |||||
} | |||||
} | |||||
public bool SupportsHeading => CLLocationManager.HeadingAvailable; | |||||
public LocationServiceImplementation() | |||||
{ | |||||
DesiredAccuracy = 100; | |||||
//_manager = GetManager(); | |||||
//_manager.AuthorizationChanged += OnAuthorizationChanged; | |||||
//_manager.Failed += OnFailed; | |||||
//_manager.UpdatedLocation += OnUpdatedLocation; | |||||
//_manager.UpdatedHeading += OnUpdatedHeading; | |||||
//_manager.DeferredUpdatesFinished += OnDeferredUpdatesFinished; | |||||
} | |||||
void OnDeferredUpdatesFinished(object sender, NSErrorEventArgs e) => _deferringUpdates = false; | |||||
#region Internal Implementation | |||||
async Task<bool> CheckPermissions(Permission permission) | |||||
{ | |||||
IPermissionsService permissionsService = new PermissionsService(); | |||||
var status = await permissionsService.CheckPermissionStatusAsync(permission); | |||||
if (status != PermissionStatus.Granted) | |||||
{ | |||||
Console.WriteLine("Currently do not have Location permissions, requesting permissions"); | |||||
var request = await permissionsService.RequestPermissionsAsync(permission); | |||||
if (request[permission] != PermissionStatus.Granted) | |||||
{ | |||||
Console.WriteLine("Location permission denied, can not get positions async."); | |||||
return false; | |||||
} | |||||
} | |||||
return true; | |||||
} | |||||
CLLocationManager GetManager() | |||||
{ | |||||
CLLocationManager manager = null; | |||||
new NSObject().InvokeOnMainThread(() => manager = new CLLocationManager()); | |||||
return manager; | |||||
} | |||||
#endregion | |||||
#region ILocationServiceImplementation | |||||
public async Task<Position> GetPositionAsync(TimeSpan? timeout, CancellationToken? cancelToken = null, bool includeHeading = false) | |||||
{ | |||||
var permission = Permission.LocationWhenInUse; | |||||
var hasPermission = await CheckPermissions(permission); | |||||
if (!hasPermission) | |||||
throw new GeolocationException(GeolocationError.Unauthorized); | |||||
var timeoutMilliseconds = timeout.HasValue ? (int)timeout.Value.TotalMilliseconds : Timeout.Infinite; | |||||
if (timeoutMilliseconds <= 0 && timeoutMilliseconds != Timeout.Infinite) | |||||
throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout must be positive or Timeout.Infinite"); | |||||
if (!cancelToken.HasValue) | |||||
cancelToken = CancellationToken.None; | |||||
TaskCompletionSource<Position> tcs; | |||||
var manager = GetManager(); | |||||
manager.DesiredAccuracy = DesiredAccuracy; | |||||
// Permit background updates if background location mode is enabled | |||||
if (UIDevice.CurrentDevice.CheckSystemVersion(9, 0)) | |||||
{ | |||||
var backgroundModes = NSBundle.MainBundle.InfoDictionary[(NSString)"UIBackgroundModes"] as NSArray; | |||||
manager.AllowsBackgroundLocationUpdates = backgroundModes != null && (backgroundModes.Contains((NSString)"Location") || backgroundModes.Contains((NSString)"location")); | |||||
} | |||||
// Always prevent location update pausing since we're only listening for a single update | |||||
if (UIDevice.CurrentDevice.CheckSystemVersion(6, 0)) | |||||
manager.PausesLocationUpdatesAutomatically = false; | |||||
tcs = new TaskCompletionSource<Position>(manager); | |||||
var singleListener = new GeolocationSingleUpdateDelegate(manager, DesiredAccuracy, includeHeading, timeoutMilliseconds, cancelToken.Value); | |||||
manager.Delegate = singleListener; | |||||
manager.StartUpdatingLocation(); | |||||
if (includeHeading && SupportsHeading) | |||||
manager.StartUpdatingHeading(); | |||||
return await singleListener.Task; | |||||
//tcs = new TaskCompletionSource<Position>(); | |||||
//if (_lastPosition == null) | |||||
//{ | |||||
// if (cancelToken != CancellationToken.None) | |||||
// cancelToken.Value.Register(() => tcs.TrySetCanceled()); | |||||
// EventHandler<PositionErrorEventArgs> gotError = null; | |||||
// gotError = (s, e) => | |||||
// { | |||||
// tcs.TrySetException(new GeolocationException(e.Error)); | |||||
// PositionError -= gotError; | |||||
// }; | |||||
// PositionError += gotError; | |||||
// EventHandler<PositionEventArgs> gotPosition = null; | |||||
// gotPosition = (s, e) => | |||||
// { | |||||
// tcs.TrySetResult(e.Position); | |||||
// PositionChanged += gotPosition; | |||||
// }; | |||||
// PositionChanged += gotPosition; | |||||
//} | |||||
//else | |||||
// tcs.SetResult(_lastPosition); | |||||
//return await tcs.Task; | |||||
} | |||||
#endregion | |||||
} | |||||
} |
@ -0,0 +1,143 @@ | |||||
using System; | |||||
using System.Collections.Generic; | |||||
using System.Threading.Tasks; | |||||
using eShopOnContainers.Core.Models.Permissions; | |||||
using eShopOnContainers.Core.Services.Permissions; | |||||
using CoreLocation; | |||||
using UIKit; | |||||
using Foundation; | |||||
namespace eShopOnContainers.iOS.Services | |||||
{ | |||||
public class PermissionsService : IPermissionsService | |||||
{ | |||||
#region Internal Implementation | |||||
PermissionStatus GetLocationPermissionStatus(Permission permission) | |||||
{ | |||||
if (!CLLocationManager.LocationServicesEnabled) | |||||
return PermissionStatus.Disabled; | |||||
var status = CLLocationManager.Status; | |||||
if (UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) | |||||
{ | |||||
switch (status) | |||||
{ | |||||
case CLAuthorizationStatus.AuthorizedAlways: | |||||
case CLAuthorizationStatus.AuthorizedWhenInUse: | |||||
return PermissionStatus.Granted; | |||||
case CLAuthorizationStatus.Denied: | |||||
return PermissionStatus.Denied; | |||||
case CLAuthorizationStatus.Restricted: | |||||
return PermissionStatus.Restricted; | |||||
default: | |||||
return PermissionStatus.Unknown; | |||||
} | |||||
} | |||||
switch (status) | |||||
{ | |||||
case CLAuthorizationStatus.Authorized: | |||||
return PermissionStatus.Granted; | |||||
case CLAuthorizationStatus.Denied: | |||||
return PermissionStatus.Denied; | |||||
case CLAuthorizationStatus.Restricted: | |||||
return PermissionStatus.Restricted; | |||||
default: | |||||
return PermissionStatus.Unknown; | |||||
} | |||||
} | |||||
Task<PermissionStatus> RequestLocationPermissionAsync(Permission permission = Permission.Location) | |||||
{ | |||||
if (CLLocationManager.Status == CLAuthorizationStatus.AuthorizedWhenInUse && permission == Permission.LocationAlways) | |||||
{ | |||||
// Don't do anything and request it | |||||
} | |||||
else if (GetLocationPermissionStatus(permission) != PermissionStatus.Unknown) | |||||
return Task.FromResult(GetLocationPermissionStatus(permission)); | |||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0)) | |||||
{ | |||||
return Task.FromResult(PermissionStatus.Unknown); | |||||
} | |||||
EventHandler<CLAuthorizationChangedEventArgs> authCallback = null; | |||||
var tcs = new TaskCompletionSource<PermissionStatus>(); | |||||
var locationManager = new CLLocationManager(); | |||||
authCallback = (sender, e) => | |||||
{ | |||||
if (e.Status == CLAuthorizationStatus.NotDetermined) | |||||
return; | |||||
locationManager.AuthorizationChanged -= authCallback; | |||||
tcs.TrySetResult(GetLocationPermissionStatus(permission)); | |||||
}; | |||||
locationManager.AuthorizationChanged += authCallback; | |||||
var info = NSBundle.MainBundle.InfoDictionary; | |||||
//if (permission == Permission.Location) | |||||
//{ | |||||
// if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) | |||||
// locationManager.RequestAlwaysAuthorization(); | |||||
// else if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) | |||||
// locationManager.RequestWhenInUseAuthorization(); | |||||
// else | |||||
// throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); | |||||
//} | |||||
//else if (permission == Permission.LocationAlways) | |||||
//{ | |||||
// if (info.ContainsKey(new NSString("NSLocationAlwaysUsageDescription"))) | |||||
// locationManager.RequestAlwaysAuthorization(); | |||||
// else | |||||
// throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates!"); | |||||
//} | |||||
if (permission == Permission.LocationWhenInUse) | |||||
{ | |||||
if (info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription"))) | |||||
locationManager.RequestWhenInUseAuthorization(); | |||||
else | |||||
throw new UnauthorizedAccessException("On iOS 8.0 and higher you must set either NSLocationWhenInUseUsageDescription or NSLocationAlwaysUsageDescription in your Info.plist file to enable Authorization Requests for Location updates."); | |||||
} | |||||
return tcs.Task; | |||||
} | |||||
#endregion | |||||
#region IPermissionsServiceImplementation | |||||
public Task<PermissionStatus> CheckPermissionStatusAsync(Permission permission) | |||||
{ | |||||
switch (permission) | |||||
{ | |||||
case Permission.LocationWhenInUse: | |||||
return Task.FromResult(GetLocationPermissionStatus(permission)); | |||||
} | |||||
return Task.FromResult(PermissionStatus.Granted); | |||||
} | |||||
public async Task<Dictionary<Permission, PermissionStatus>> RequestPermissionsAsync(params Permission[] permissions) | |||||
{ | |||||
var results = new Dictionary<Permission, PermissionStatus>(); | |||||
foreach (var permission in permissions) | |||||
{ | |||||
if (results.ContainsKey(permission)) | |||||
continue; | |||||
switch (permission) | |||||
{ | |||||
case Permission.LocationWhenInUse: | |||||
results.Add(permission, await RequestLocationPermissionAsync(permission).ConfigureAwait(false)); | |||||
break; | |||||
} | |||||
if (!results.ContainsKey(permission)) | |||||
results.Add(permission, PermissionStatus.Granted); | |||||
} | |||||
return results; | |||||
} | |||||
#endregion | |||||
} | |||||
} |