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