GH-7: Geolocation API's (#132)
* First pass at Android Geolocation impl * No need for nullables * Added the geolocation sample * Added the UWP geolocation implementation * Getting some of the iOS in * Use a global location manager so that the permissions popup lives for mor than a few seconds * Check before asking on iOS * Finished the iOS implementation and tweak a few things * Remove the unsused using * Don't use locking and static fields, use the TCS state * Keep the manager alive for the duration of the method * Use platform specific accuracy * Moving files after merge * Changing namespaces * Removed the `ConfigureAwait(false)` instances * Use extension methods * tabs not spaces * Added sopme docs * More docs * Added some tests (that can't run yet) * Enabling the tests for CI after adding attributes * Added the iOS permissions text to the tests * iOS has opinions when it comes to locations * Starting the location manager on the main thread, but return on the original thread * We just need to call the constructor in the main thread * Added all the permissions to the manifest for the samples and the tests * Android has looper issues * Location is now a declared permission * Changes based on feedback. * Cleanup iOS Permissions. Must have static location manager around. * The stylish copper got us again
This commit is contained in:
Родитель
f2420c8099
Коммит
f185ef419f
|
@ -1,7 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0.1.0" package="com.xamarin.essentials.devicetests" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<application android:label="@string/app_name" android:icon="@drawable/icon" android:theme="@style/MainTheme"></application>
|
||||
</manifest>
|
|
@ -28,7 +28,3 @@ using Android.App;
|
|||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Add some common permissions, these can be removed if not needed
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
<Compile Include="$(MSBuildThisFileDirectory)DeviceInfo_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Flashlight_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geocoding_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Geolocation_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)Permissions_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)PhoneDialer_Tests.cs" />
|
||||
<Compile Include="$(MSBuildThisFileDirectory)ScreenLock_Tests.cs" />
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Xamarin.Essentials;
|
||||
using Xunit;
|
||||
|
||||
namespace DeviceTests
|
||||
{
|
||||
// TEST NOTES:
|
||||
// - a human needs to accept permissions
|
||||
public class Geolocation_Tests
|
||||
{
|
||||
[Fact]
|
||||
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
|
||||
public async Task Get_LastKnownLocation_Is_Something()
|
||||
{
|
||||
var location = await Geolocation.GetLastKnownLocationAsync();
|
||||
|
||||
Assert.NotNull(location);
|
||||
|
||||
Assert.True(location.Accuracy > 0);
|
||||
Assert.NotEqual(0.0, location.Latitude);
|
||||
Assert.NotEqual(0.0, location.Longitude);
|
||||
|
||||
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
|
||||
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
|
||||
|
||||
// before right now, but after yesterday
|
||||
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
|
||||
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
|
||||
public async Task Get_Location_Is_Something()
|
||||
{
|
||||
var location = await Geolocation.GetLocationAsync();
|
||||
|
||||
Assert.NotNull(location);
|
||||
|
||||
Assert.True(location.Accuracy > 0);
|
||||
Assert.NotEqual(0.0, location.Latitude);
|
||||
Assert.NotEqual(0.0, location.Longitude);
|
||||
|
||||
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
|
||||
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
|
||||
|
||||
// before right now, but after yesterday
|
||||
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
|
||||
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
|
||||
public async Task Get_Location_With_Request_Is_Something()
|
||||
{
|
||||
var request = new GeolocationRequest(GeolocationAccuracy.Best);
|
||||
var location = await Geolocation.GetLocationAsync(request);
|
||||
|
||||
Assert.NotNull(location);
|
||||
|
||||
Assert.True(location.Accuracy > 0);
|
||||
Assert.NotEqual(0.0, location.Latitude);
|
||||
Assert.NotEqual(0.0, location.Longitude);
|
||||
|
||||
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
|
||||
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
|
||||
|
||||
// before right now, but after yesterday
|
||||
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
|
||||
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,10 +5,6 @@ using System.Threading.Tasks;
|
|||
using Xamarin.Essentials;
|
||||
using Xunit;
|
||||
|
||||
#if __ANDROID__
|
||||
[assembly: Android.App.UsesPermission(Android.Manifest.Permission.BatteryStats)]
|
||||
#endif
|
||||
|
||||
namespace DeviceTests
|
||||
{
|
||||
public class Permissions_Tests
|
||||
|
@ -16,23 +12,12 @@ namespace DeviceTests
|
|||
[Theory]
|
||||
[InlineData(PermissionType.Battery)]
|
||||
[InlineData(PermissionType.NetworkState)]
|
||||
[InlineData(PermissionType.LocationWhenInUse)]
|
||||
internal void Ensure_Declared(PermissionType permission)
|
||||
{
|
||||
Permissions.EnsureDeclared(permission);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.LocationWhenInUse)]
|
||||
internal void Ensure_Declared_Throws(PermissionType permission)
|
||||
{
|
||||
if (DeviceInfo.Platform == DeviceInfo.Platforms.UWP)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Throws<PermissionException>(() => Permissions.EnsureDeclared(permission));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.Battery, PermissionStatus.Granted)]
|
||||
[InlineData(PermissionType.NetworkState, PermissionStatus.Granted)]
|
||||
|
@ -43,18 +28,6 @@ namespace DeviceTests
|
|||
Assert.Equal(expectedStatus, status);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.LocationWhenInUse)]
|
||||
internal Task Check_Status_Throws(PermissionType permission)
|
||||
{
|
||||
if (DeviceInfo.Platform == DeviceInfo.Platforms.UWP)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
return Assert.ThrowsAsync<PermissionException>(async () => await Permissions.CheckStatusAsync(permission));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PermissionType.Battery, PermissionStatus.Granted)]
|
||||
[InlineData(PermissionType.NetworkState, PermissionStatus.Granted)]
|
||||
|
|
|
@ -53,5 +53,7 @@
|
|||
<string>DeviceTests</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1.0</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Access to your location is required for cool things to happen!</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.essentials" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<application android:label="@string/app_name" android:icon="@drawable/icon" android:theme="@style/MainTheme"></application>
|
||||
</manifest>
|
|
@ -27,7 +27,3 @@ using Android.App;
|
|||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Add some common permissions, these can be removed if not needed
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||
|
|
|
@ -24,5 +24,6 @@
|
|||
</Applications>
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<DeviceCapability Name="location" />
|
||||
</Capabilities>
|
||||
</Package>
|
|
@ -53,5 +53,7 @@
|
|||
<string>Xamarin.Essentials</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>Access to your location is required for cool things to happen!</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
|
||||
xmlns:views="clr-namespace:Samples.View"
|
||||
xmlns:viewmodels="clr-namespace:Samples.ViewModel"
|
||||
x:Class="Samples.View.GeolocationPage"
|
||||
Title="Geolocation">
|
||||
<ContentPage.BindingContext>
|
||||
<viewmodels:GeolocationViewModel />
|
||||
</ContentPage.BindingContext>
|
||||
|
||||
<StackLayout>
|
||||
<Label Text="Quickly get the current location." FontAttributes="Bold" Margin="12" />
|
||||
|
||||
<ScrollView>
|
||||
<StackLayout Padding="12,0,12,12" Spacing="6">
|
||||
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" />
|
||||
|
||||
<Label Text="Last Known Location:" FontAttributes="Bold" Margin="0,6,0,0" />
|
||||
<Label Text="{Binding LastLocation}" />
|
||||
<Button Text="Refresh" Command="{Binding GetLastLocationCommand}" IsEnabled="{Binding IsNotBusy}" />
|
||||
|
||||
<Label Text="Current Location:" FontAttributes="Bold" Margin="0,6,0,0" />
|
||||
<Label Text="{Binding CurrentLocation}" />
|
||||
<Label Text="Accuracy:" />
|
||||
<Picker ItemsSource="{Binding Accuracies}"
|
||||
SelectedIndex="{Binding Accuracy, Mode=TwoWay}"
|
||||
IsEnabled="{Binding IsNotBusy}"
|
||||
HorizontalOptions="FillAndExpand" />
|
||||
<Button Text="Refresh" Command="{Binding GetCurrentLocationCommand}" IsEnabled="{Binding IsNotBusy}" />
|
||||
</StackLayout>
|
||||
</ScrollView>
|
||||
</StackLayout>
|
||||
|
||||
</views:BasePage>
|
|
@ -0,0 +1,10 @@
|
|||
namespace Samples.View
|
||||
{
|
||||
public partial class GeolocationPage : BasePage
|
||||
{
|
||||
public GeolocationPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Windows.Input;
|
||||
using Xamarin.Essentials;
|
||||
using Xamarin.Forms;
|
||||
|
||||
namespace Samples.ViewModel
|
||||
{
|
||||
public class GeolocationViewModel : BaseViewModel
|
||||
{
|
||||
string lastLocation;
|
||||
string currentLocation;
|
||||
int accuracy = (int)GeolocationAccuracy.Medium;
|
||||
|
||||
public GeolocationViewModel()
|
||||
{
|
||||
GetLastLocationCommand = new Command(OnGetLastLocation);
|
||||
GetCurrentLocationCommand = new Command(OnGetCurrentLocation);
|
||||
}
|
||||
|
||||
public ICommand GetLastLocationCommand { get; }
|
||||
|
||||
public ICommand GetCurrentLocationCommand { get; }
|
||||
|
||||
public string LastLocation
|
||||
{
|
||||
get => lastLocation;
|
||||
set => SetProperty(ref lastLocation, value);
|
||||
}
|
||||
|
||||
public string CurrentLocation
|
||||
{
|
||||
get => currentLocation;
|
||||
set => SetProperty(ref currentLocation, value);
|
||||
}
|
||||
|
||||
public string[] Accuracies
|
||||
=> Enum.GetNames(typeof(GeolocationAccuracy));
|
||||
|
||||
public int Accuracy
|
||||
{
|
||||
get => accuracy;
|
||||
set => SetProperty(ref accuracy, value);
|
||||
}
|
||||
|
||||
async void OnGetLastLocation()
|
||||
{
|
||||
if (IsBusy)
|
||||
return;
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var location = await Geolocation.GetLastKnownLocationAsync();
|
||||
LastLocation = FormatLocation(location);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LastLocation = FormatLocation(null);
|
||||
}
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
async void OnGetCurrentLocation()
|
||||
{
|
||||
if (IsBusy)
|
||||
return;
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
var request = new GeolocationRequest((GeolocationAccuracy)Accuracy);
|
||||
var location = await Geolocation.GetLocationAsync(request);
|
||||
CurrentLocation = FormatLocation(location);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
CurrentLocation = FormatLocation(null);
|
||||
}
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
private string FormatLocation(Location location)
|
||||
{
|
||||
if (location == null)
|
||||
{
|
||||
return "Unable to detect location.";
|
||||
}
|
||||
|
||||
return
|
||||
$"Latitude: {location.Latitude}\n" +
|
||||
$"Longitude: {location.Longitude}\n" +
|
||||
$"Accuracy: {location.Accuracy}\n" +
|
||||
$"Date (UTC): {location.TimestampUtc:d}\n" +
|
||||
$"Time (UTC): {location.TimestampUtc:T}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,7 @@ namespace Samples.ViewModel
|
|||
new SampleItem("File System", typeof(FileSystemPage), "Easily save files to app data."),
|
||||
new SampleItem("Flashlight", typeof(FlashlightPage), "A simple way to turn the flashlight on/off."),
|
||||
new SampleItem("Geocoding", typeof(GeocodingPage), "Easily geocode and reverse geocoding."),
|
||||
new SampleItem("Geolocation", typeof(GeolocationPage), "Quickly get the current location."),
|
||||
new SampleItem("Gyroscope", typeof(GyroscopePage), "Retrieve rotation around the device's three primary axes."),
|
||||
new SampleItem("Magnetometer", typeof(MagnetometerPage), "Detect device's orientation relative to Earth's magnetic field."),
|
||||
new SampleItem("Phone Dialer", typeof(PhoneDialerPage), "Easily open phone dialer."),
|
||||
|
|
|
@ -0,0 +1,194 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Android.Locations;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
|
||||
using AndroidLocation = Android.Locations.Location;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class Geolocation
|
||||
{
|
||||
const long twoMinutes = 120000;
|
||||
|
||||
static async Task<Location> PlatformLastKnownLocationAsync()
|
||||
{
|
||||
await Permissions.RequireAsync(PermissionType.LocationWhenInUse);
|
||||
|
||||
var lm = Platform.LocationManager;
|
||||
AndroidLocation bestLocation = null;
|
||||
|
||||
foreach (var provider in lm.GetProviders(true))
|
||||
{
|
||||
var location = lm.GetLastKnownLocation(provider);
|
||||
|
||||
if (bestLocation == null || IsBetterLocation(location, bestLocation))
|
||||
bestLocation = location;
|
||||
}
|
||||
|
||||
if (bestLocation == null)
|
||||
return null;
|
||||
|
||||
return bestLocation.ToLocation();
|
||||
}
|
||||
|
||||
static async Task<Location> PlatformLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await Permissions.RequireAsync(PermissionType.LocationWhenInUse);
|
||||
|
||||
var locationManager = Platform.LocationManager;
|
||||
|
||||
// get the best possible provider for the requested accuracy
|
||||
var provider = GetBestProvider(locationManager, request.DesiredAccuracy);
|
||||
|
||||
// if no providers exist, we can't get a location
|
||||
// let's punt and try to get the last known location
|
||||
if (string.IsNullOrEmpty(provider))
|
||||
return await GetLastKnownLocationAsync();
|
||||
|
||||
var tcs = new TaskCompletionSource<AndroidLocation>();
|
||||
|
||||
var listener = new SingleLocationListener();
|
||||
listener.LocationHandler = HandleLocation;
|
||||
|
||||
cancellationToken = Utils.TimeoutToken(cancellationToken, request.Timeout);
|
||||
cancellationToken.Register(Cancel);
|
||||
|
||||
// start getting location updates
|
||||
// make sure to use a thread with a looper
|
||||
var looper = Looper.MyLooper() ?? Looper.MainLooper;
|
||||
locationManager.RequestLocationUpdates(provider, 0, 0, listener, looper);
|
||||
|
||||
var androidLocation = await tcs.Task;
|
||||
|
||||
if (androidLocation == null)
|
||||
return null;
|
||||
|
||||
return androidLocation.ToLocation();
|
||||
|
||||
void HandleLocation(AndroidLocation location)
|
||||
{
|
||||
RemoveUpdates();
|
||||
tcs.TrySetResult(location);
|
||||
}
|
||||
|
||||
void Cancel()
|
||||
{
|
||||
RemoveUpdates();
|
||||
tcs.TrySetResult(null);
|
||||
}
|
||||
|
||||
void RemoveUpdates()
|
||||
{
|
||||
locationManager.RemoveUpdates(listener);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleLocationListener : Java.Lang.Object, ILocationListener
|
||||
{
|
||||
bool wasRaised = false;
|
||||
|
||||
public Action<AndroidLocation> LocationHandler { get; set; }
|
||||
|
||||
public void OnLocationChanged(AndroidLocation location)
|
||||
{
|
||||
if (wasRaised)
|
||||
return;
|
||||
|
||||
wasRaised = true;
|
||||
|
||||
LocationHandler?.Invoke(location);
|
||||
}
|
||||
|
||||
public void OnProviderDisabled(string provider)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnProviderEnabled(string provider)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
static string GetBestProvider(LocationManager locationManager, GeolocationAccuracy accuracy)
|
||||
{
|
||||
var criteria = new Criteria();
|
||||
criteria.BearingRequired = false;
|
||||
criteria.AltitudeRequired = false;
|
||||
criteria.SpeedRequired = false;
|
||||
|
||||
switch (accuracy)
|
||||
{
|
||||
case GeolocationAccuracy.Lowest:
|
||||
criteria.Accuracy = Accuracy.NoRequirement;
|
||||
criteria.HorizontalAccuracy = Accuracy.NoRequirement;
|
||||
criteria.PowerRequirement = Power.NoRequirement;
|
||||
break;
|
||||
case GeolocationAccuracy.Low:
|
||||
criteria.Accuracy = Accuracy.Low;
|
||||
criteria.HorizontalAccuracy = Accuracy.Low;
|
||||
criteria.PowerRequirement = Power.Low;
|
||||
break;
|
||||
case GeolocationAccuracy.Medium:
|
||||
criteria.Accuracy = Accuracy.Medium;
|
||||
criteria.HorizontalAccuracy = Accuracy.Medium;
|
||||
criteria.PowerRequirement = Power.Medium;
|
||||
break;
|
||||
case GeolocationAccuracy.High:
|
||||
criteria.Accuracy = Accuracy.High;
|
||||
criteria.HorizontalAccuracy = Accuracy.High;
|
||||
criteria.PowerRequirement = Power.High;
|
||||
break;
|
||||
case GeolocationAccuracy.Best:
|
||||
criteria.Accuracy = Accuracy.Fine;
|
||||
criteria.HorizontalAccuracy = Accuracy.Fine;
|
||||
criteria.PowerRequirement = Power.High;
|
||||
break;
|
||||
}
|
||||
|
||||
return locationManager.GetBestProvider(criteria, true) ?? locationManager.GetProviders(true).FirstOrDefault();
|
||||
}
|
||||
|
||||
internal static bool IsBetterLocation(AndroidLocation location, AndroidLocation 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 = location?.Provider?.Equals(bestLocation?.Provider, StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
|
||||
if (isMoreAccurate)
|
||||
return true;
|
||||
|
||||
if (isNewer && !isLessAccurate)
|
||||
return true;
|
||||
|
||||
if (isNewer && !isSignificantlyLessAccurage && isFromSameProvider)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using CoreLocation;
|
||||
using Foundation;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class Geolocation
|
||||
{
|
||||
internal static bool IsSupported
|
||||
=> CLLocationManager.LocationServicesEnabled;
|
||||
|
||||
static async Task<Location> PlatformLastKnownLocationAsync()
|
||||
{
|
||||
await Permissions.RequireAsync(PermissionType.LocationWhenInUse);
|
||||
|
||||
var manager = new CLLocationManager();
|
||||
var location = manager.Location;
|
||||
|
||||
return location.ToLocation();
|
||||
}
|
||||
|
||||
static async Task<Location> PlatformLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await Permissions.RequireAsync(PermissionType.LocationWhenInUse);
|
||||
|
||||
// the location manager requires an active run loop
|
||||
// so just use the main loop
|
||||
CLLocationManager manager = null;
|
||||
NSRunLoop.Main.InvokeOnMainThread(() => manager = new CLLocationManager());
|
||||
|
||||
var tcs = new TaskCompletionSource<CLLocation>(manager);
|
||||
|
||||
var listener = new SingleLocationListener();
|
||||
listener.LocationHandler += HandleLocation;
|
||||
|
||||
cancellationToken = Utils.TimeoutToken(cancellationToken, request.Timeout);
|
||||
cancellationToken.Register(Cancel);
|
||||
|
||||
manager.DesiredAccuracy = request.PlatformDesiredAccuracy;
|
||||
manager.Delegate = listener;
|
||||
|
||||
// we're only listening for a single update
|
||||
manager.PausesLocationUpdatesAutomatically = false;
|
||||
|
||||
manager.StartUpdatingLocation();
|
||||
|
||||
var clLocation = await tcs.Task;
|
||||
|
||||
if (clLocation == null)
|
||||
return null;
|
||||
|
||||
return clLocation.ToLocation();
|
||||
|
||||
void HandleLocation(CLLocation location)
|
||||
{
|
||||
manager.StopUpdatingLocation();
|
||||
tcs.TrySetResult(location);
|
||||
}
|
||||
|
||||
void Cancel()
|
||||
{
|
||||
manager.StopUpdatingLocation();
|
||||
tcs.TrySetResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
class SingleLocationListener : CLLocationManagerDelegate
|
||||
{
|
||||
bool wasRaised = false;
|
||||
|
||||
public Action<CLLocation> LocationHandler { get; set; }
|
||||
|
||||
public override void LocationsUpdated(CLLocationManager manager, CLLocation[] locations)
|
||||
{
|
||||
if (wasRaised)
|
||||
return;
|
||||
|
||||
wasRaised = true;
|
||||
|
||||
var location = locations.LastOrDefault();
|
||||
|
||||
if (location == null)
|
||||
return;
|
||||
|
||||
LocationHandler?.Invoke(location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class Geolocation
|
||||
{
|
||||
static Task<Location> PlatformLastKnownLocationAsync() =>
|
||||
throw new NotImplementedInReferenceAssemblyException();
|
||||
|
||||
static Task<Location> PlatformLocationAsync(GeolocationRequest request, CancellationToken cancellationToken) =>
|
||||
throw new NotImplementedInReferenceAssemblyException();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class Geolocation
|
||||
{
|
||||
public static Task<Location> GetLastKnownLocationAsync() =>
|
||||
PlatformLastKnownLocationAsync();
|
||||
|
||||
public static Task<Location> GetLocationAsync() =>
|
||||
PlatformLocationAsync(new GeolocationRequest(), default);
|
||||
|
||||
public static Task<Location> GetLocationAsync(GeolocationRequest request) =>
|
||||
PlatformLocationAsync(request ?? new GeolocationRequest(), default);
|
||||
|
||||
public static Task<Location> GetLocationAsync(GeolocationRequest request, CancellationToken cancelToken) =>
|
||||
PlatformLocationAsync(request ?? new GeolocationRequest(), cancelToken);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Devices.Geolocation;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class Geolocation
|
||||
{
|
||||
static async Task<Location> PlatformLastKnownLocationAsync()
|
||||
{
|
||||
// no need for permissions as AllowFallbackToConsentlessPositions
|
||||
// will allow the device to return a location regardless
|
||||
|
||||
var geolocator = new Geolocator
|
||||
{
|
||||
DesiredAccuracy = PositionAccuracy.Default,
|
||||
};
|
||||
geolocator.AllowFallbackToConsentlessPositions();
|
||||
|
||||
var location = await geolocator.GetGeopositionAsync().AsTask();
|
||||
|
||||
if (location?.Coordinate == null)
|
||||
return null;
|
||||
|
||||
return location.ToLocation();
|
||||
}
|
||||
|
||||
static async Task<Location> PlatformLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
await Permissions.RequireAsync(PermissionType.LocationWhenInUse);
|
||||
|
||||
var geolocator = new Geolocator
|
||||
{
|
||||
DesiredAccuracyInMeters = request.PlatformDesiredAccuracy
|
||||
};
|
||||
|
||||
cancellationToken = Utils.TimeoutToken(cancellationToken, request.Timeout);
|
||||
|
||||
var location = await geolocator.GetGeopositionAsync().AsTask(cancellationToken);
|
||||
|
||||
if (location?.Coordinate == null)
|
||||
return null;
|
||||
|
||||
return new Location
|
||||
{
|
||||
Latitude = location.Coordinate.Point.Position.Latitude,
|
||||
Longitude = location.Coordinate.Point.Position.Longitude,
|
||||
TimestampUtc = location.Coordinate.Timestamp,
|
||||
Accuracy = location.Coordinate.Accuracy
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using CoreLocation;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public partial class GeolocationRequest
|
||||
{
|
||||
internal double PlatformDesiredAccuracy
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (DesiredAccuracy)
|
||||
{
|
||||
case GeolocationAccuracy.Lowest:
|
||||
return CLLocation.AccuracyThreeKilometers;
|
||||
case GeolocationAccuracy.Low:
|
||||
return CLLocation.AccuracyKilometer;
|
||||
case GeolocationAccuracy.Medium:
|
||||
return CLLocation.AccuracyHundredMeters;
|
||||
case GeolocationAccuracy.High:
|
||||
return CLLocation.AccuracyNearestTenMeters;
|
||||
case GeolocationAccuracy.Best:
|
||||
return CLLocation.AccurracyBestForNavigation;
|
||||
default:
|
||||
return CLLocation.AccuracyHundredMeters;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public enum GeolocationAccuracy
|
||||
{
|
||||
// iOS: ThreeKilometers (3000m)
|
||||
// Android: ACCURACY_LOW, POWER_LOW (500m)
|
||||
// UWP: 3000 (1000-5000m)
|
||||
Lowest,
|
||||
|
||||
// iOS: Kilometer (1000m)
|
||||
// Android: ACCURACY_LOW, POWER_MED (500m)
|
||||
// UWP: 1000 (300-3000m)
|
||||
Low,
|
||||
|
||||
// iOS: HundredMeters (100m)
|
||||
// Android: ACCURACY_MED, POWER_MED (100-500m)
|
||||
// UWP: 100 (30-500m)
|
||||
Medium,
|
||||
|
||||
// iOS: NearestTenMeters (10m)
|
||||
// Android: ACCURACY_HI, POWER_MED (0-100m)
|
||||
// UWP: High (<=10m)
|
||||
High,
|
||||
|
||||
// iOS: Best (0m)
|
||||
// Android: ACCURACY_HI, POWER_HI (0-100m)
|
||||
// UWP: High (<=10m)
|
||||
Best
|
||||
}
|
||||
|
||||
public partial class GeolocationRequest
|
||||
{
|
||||
public GeolocationRequest()
|
||||
{
|
||||
Timeout = TimeSpan.Zero;
|
||||
DesiredAccuracy = GeolocationAccuracy.Medium;
|
||||
}
|
||||
|
||||
public GeolocationRequest(GeolocationAccuracy accuracy)
|
||||
{
|
||||
Timeout = TimeSpan.Zero;
|
||||
DesiredAccuracy = accuracy;
|
||||
}
|
||||
|
||||
public GeolocationRequest(GeolocationAccuracy accuracy, TimeSpan timeout)
|
||||
{
|
||||
Timeout = timeout;
|
||||
DesiredAccuracy = accuracy;
|
||||
}
|
||||
|
||||
public TimeSpan Timeout { get; set; }
|
||||
|
||||
public GeolocationAccuracy DesiredAccuracy { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public partial class GeolocationRequest
|
||||
{
|
||||
internal uint PlatformDesiredAccuracy
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (DesiredAccuracy)
|
||||
{
|
||||
case GeolocationAccuracy.Lowest:
|
||||
return 3000;
|
||||
case GeolocationAccuracy.Low:
|
||||
return 1000;
|
||||
case GeolocationAccuracy.Medium:
|
||||
return 100;
|
||||
case GeolocationAccuracy.High:
|
||||
return 10; // Equivalent to PositionAccuracy.High
|
||||
case GeolocationAccuracy.Best:
|
||||
return 1;
|
||||
default:
|
||||
return 500; // Equivalent to PositionAccuracy.Default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
using System.Threading.Tasks;
|
||||
using CoreLocation;
|
||||
using Foundation;
|
||||
using UIKit;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
|
@ -9,16 +8,12 @@ namespace Xamarin.Essentials
|
|||
{
|
||||
static void PlatformEnsureDeclared(PermissionType permission)
|
||||
{
|
||||
// Info.plist declarations were only required in >= iOS 8.0
|
||||
if (!Platform.HasOSVersion(8, 0))
|
||||
return;
|
||||
|
||||
var info = NSBundle.MainBundle.InfoDictionary;
|
||||
|
||||
if (permission == PermissionType.LocationWhenInUse)
|
||||
{
|
||||
if (!info.ContainsKey(new NSString("NSLocationWhenInUseUsageDescription")))
|
||||
throw new PermissionException("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!");
|
||||
throw new PermissionException("On iOS 8.0 and higher you must set either `NSLocationWhenInUseUsageDescription` in your Info.plist file to enable Authorization Requests for Location updates!");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,17 +30,21 @@ namespace Xamarin.Essentials
|
|||
return Task.FromResult(PermissionStatus.Granted);
|
||||
}
|
||||
|
||||
static Task<PermissionStatus> PlatformRequestAsync(PermissionType permission)
|
||||
static async Task<PermissionStatus> PlatformRequestAsync(PermissionType permission)
|
||||
{
|
||||
// Check status before requesting first
|
||||
if (await PlatformCheckStatusAsync(permission) == PermissionStatus.Granted)
|
||||
return PermissionStatus.Granted;
|
||||
|
||||
PlatformEnsureDeclared(permission);
|
||||
|
||||
switch (permission)
|
||||
{
|
||||
case PermissionType.LocationWhenInUse:
|
||||
return RequestLocationAsync();
|
||||
return await RequestLocationAsync();
|
||||
}
|
||||
|
||||
return Task.FromResult(PermissionStatus.Granted);
|
||||
return PermissionStatus.Granted;
|
||||
}
|
||||
|
||||
static PermissionStatus GetLocationStatus()
|
||||
|
@ -55,25 +54,10 @@ namespace Xamarin.Essentials
|
|||
|
||||
var status = CLLocationManager.Status;
|
||||
|
||||
if (Platform.HasOSVersion(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:
|
||||
case CLAuthorizationStatus.AuthorizedAlways:
|
||||
case CLAuthorizationStatus.AuthorizedWhenInUse:
|
||||
return PermissionStatus.Granted;
|
||||
case CLAuthorizationStatus.Denied:
|
||||
return PermissionStatus.Denied;
|
||||
|
@ -84,30 +68,29 @@ namespace Xamarin.Essentials
|
|||
}
|
||||
}
|
||||
|
||||
static CLLocationManager locationManager;
|
||||
|
||||
static Task<PermissionStatus> RequestLocationAsync()
|
||||
{
|
||||
if (!UIDevice.CurrentDevice.CheckSystemVersion(8, 0))
|
||||
return Task.FromResult(PermissionStatus.Unknown);
|
||||
locationManager = new CLLocationManager();
|
||||
|
||||
var locationManager = new CLLocationManager();
|
||||
var tcs = new TaskCompletionSource<PermissionStatus>(locationManager);
|
||||
|
||||
var tcs = new TaskCompletionSource<PermissionStatus>();
|
||||
locationManager.AuthorizationChanged += LocationAuthCallback;
|
||||
locationManager.RequestWhenInUseAuthorization();
|
||||
|
||||
void AuthCallback(object sender, CLAuthorizationChangedEventArgs e)
|
||||
return tcs.Task;
|
||||
|
||||
void LocationAuthCallback(object sender, CLAuthorizationChangedEventArgs e)
|
||||
{
|
||||
if (e.Status == CLAuthorizationStatus.NotDetermined)
|
||||
return;
|
||||
|
||||
locationManager.AuthorizationChanged -= AuthCallback;
|
||||
|
||||
locationManager.AuthorizationChanged -= LocationAuthCallback;
|
||||
tcs.TrySetResult(GetLocationStatus());
|
||||
locationManager.Dispose();
|
||||
locationManager = null;
|
||||
}
|
||||
|
||||
locationManager.AuthorizationChanged += AuthCallback;
|
||||
|
||||
locationManager.RequestWhenInUseAuthorization();
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ using Android.Content;
|
|||
using Android.Content.PM;
|
||||
using Android.Hardware;
|
||||
using Android.Hardware.Camera2;
|
||||
using Android.Locations;
|
||||
using Android.Net;
|
||||
using Android.Net.Wifi;
|
||||
using Android.OS;
|
||||
|
@ -74,7 +75,7 @@ namespace Xamarin.Essentials
|
|||
Application.Context.GetSystemService(Context.ConnectivityService) as ConnectivityManager;
|
||||
|
||||
internal static Vibrator Vibrator =>
|
||||
(Vibrator)Application.Context.GetSystemService(Context.VibratorService);
|
||||
Application.Context.GetSystemService(Context.VibratorService) as Vibrator;
|
||||
|
||||
internal static WifiManager WifiManager =>
|
||||
Application.Context.GetSystemService(Context.WifiService) as WifiManager;
|
||||
|
@ -84,6 +85,9 @@ namespace Xamarin.Essentials
|
|||
|
||||
internal static ClipboardManager ClipboardManager =>
|
||||
Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||
|
||||
internal static LocationManager LocationManager =>
|
||||
Application.Context.GetSystemService(Context.LocationService) as LocationManager;
|
||||
}
|
||||
|
||||
class ActivityLifecycleContextListener : Java.Lang.Object, Application.IActivityLifecycleCallbacks
|
||||
|
|
|
@ -19,6 +19,7 @@ namespace Xamarin.Essentials
|
|||
return null;
|
||||
|
||||
var provider = new DataProtectionProvider();
|
||||
|
||||
var buffer = await provider.UnprotectAsync(encBytes.AsBuffer());
|
||||
|
||||
return Encoding.UTF8.GetString(buffer.ToArray());
|
||||
|
@ -32,7 +33,9 @@ namespace Xamarin.Essentials
|
|||
|
||||
// LOCAL=user and LOCAL=machine do not require enterprise auth capability
|
||||
var provider = new DataProtectionProvider("LOCAL=user");
|
||||
|
||||
var buffer = await provider.ProtectAsync(bytes.AsBuffer());
|
||||
|
||||
var encBytes = buffer.ToArray();
|
||||
|
||||
settings.Values[key] = encBytes;
|
||||
|
|
|
@ -46,6 +46,8 @@ namespace Xamarin.Essentials
|
|||
|
||||
public double Longitude { get; set; }
|
||||
|
||||
public double? Accuracy { get; set; }
|
||||
|
||||
public static double CalculateDistance(Location locationStart, Location locationEnd, DistanceUnits units) =>
|
||||
CalculateDistance(locationStart.Latitude, locationStart.Longitude, locationEnd.Latitude, locationEnd.Longitude, units);
|
||||
|
||||
|
|
|
@ -10,13 +10,24 @@ namespace Xamarin.Essentials
|
|||
public static partial class LocationExtensions
|
||||
{
|
||||
internal static Location ToLocation(this AndroidAddress address) =>
|
||||
new Location(address.Latitude, address.Longitude, DateTimeOffset.UtcNow);
|
||||
new Location
|
||||
{
|
||||
Latitude = address.Latitude,
|
||||
Longitude = address.Longitude,
|
||||
TimestampUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
internal static IEnumerable<Location> ToLocations(this IEnumerable<AndroidAddress> addresses) =>
|
||||
addresses?.Select(a => a.ToLocation());
|
||||
|
||||
internal static Location ToLocation(this AndroidLocation location) =>
|
||||
new Location(location.Latitude, location.Longitude, location.GetTimestamp().ToUniversalTime());
|
||||
new Location
|
||||
{
|
||||
Latitude = location.Latitude,
|
||||
Longitude = location.Longitude,
|
||||
TimestampUtc = location.GetTimestamp().ToUniversalTime(),
|
||||
Accuracy = location.HasAccuracy ? location.Accuracy : (float?)null
|
||||
};
|
||||
|
||||
static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
|
|
|
@ -2,15 +2,42 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CoreLocation;
|
||||
using Foundation;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
public static partial class LocationExtensions
|
||||
{
|
||||
internal static Location ToLocation(this CLPlacemark placemark) =>
|
||||
new Location(placemark.Location.Coordinate.Latitude, placemark.Location.Coordinate.Longitude, DateTimeOffset.UtcNow);
|
||||
new Location
|
||||
{
|
||||
Latitude = placemark.Location.Coordinate.Latitude,
|
||||
Longitude = placemark.Location.Coordinate.Longitude,
|
||||
TimestampUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
internal static IEnumerable<Location> ToLocations(this IEnumerable<CLPlacemark> placemarks) =>
|
||||
placemarks?.Select(a => a.ToLocation());
|
||||
|
||||
internal static Location ToLocation(this CLLocation location) =>
|
||||
new Location
|
||||
{
|
||||
Latitude = location.Coordinate.Latitude,
|
||||
Longitude = location.Coordinate.Longitude,
|
||||
Accuracy = location.HorizontalAccuracy,
|
||||
TimestampUtc = location.Timestamp.ToDateTime()
|
||||
};
|
||||
|
||||
internal static DateTimeOffset ToDateTime(this NSDate timestamp)
|
||||
{
|
||||
try
|
||||
{
|
||||
return new DateTimeOffset((DateTime)timestamp);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DateTimeOffset.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Devices.Geolocation;
|
||||
using Windows.Services.Maps;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
|
@ -8,12 +9,26 @@ namespace Xamarin.Essentials
|
|||
public static partial class LocationExtensions
|
||||
{
|
||||
internal static Location ToLocation(this MapLocation mapLocation) =>
|
||||
new Location(mapLocation.Point.Position.Latitude, mapLocation.Point.Position.Longitude, DateTimeOffset.UtcNow);
|
||||
new Location
|
||||
{
|
||||
Latitude = mapLocation.Point.Position.Latitude,
|
||||
Longitude = mapLocation.Point.Position.Longitude,
|
||||
TimestampUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
internal static IEnumerable<Location> ToLocations(this IEnumerable<MapLocation> mapLocations) =>
|
||||
mapLocations?.Select(a => a.ToLocation());
|
||||
|
||||
internal static IEnumerable<Location> ToLocations(this MapLocationFinderResult result) =>
|
||||
result?.ToLocations();
|
||||
|
||||
internal static Location ToLocation(this Geoposition location) =>
|
||||
new Location
|
||||
{
|
||||
Latitude = location.Coordinate.Point.Position.Latitude,
|
||||
Longitude = location.Coordinate.Point.Position.Longitude,
|
||||
TimestampUtc = location.Coordinate.Timestamp,
|
||||
Accuracy = location.Coordinate.Accuracy
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
using System.Security.Cryptography;
|
||||
#endif
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Xamarin.Essentials
|
||||
{
|
||||
|
@ -31,5 +32,18 @@ namespace Xamarin.Essentials
|
|||
return hash.ToString();
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static CancellationToken TimeoutToken(CancellationToken cancellationToken, TimeSpan timeout)
|
||||
{
|
||||
// create a new linked cancellation token source
|
||||
var cancelTokenSrc = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
// if a timeout was given, make the token source cancel after it expires
|
||||
if (timeout > TimeSpan.Zero)
|
||||
cancelTokenSrc.CancelAfter(timeout);
|
||||
|
||||
// our Cancel method will handle the actual cancellation logic
|
||||
return cancelTokenSrc.Token;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<Interfaces />
|
||||
<Docs>
|
||||
<summary>Event arguments containing the current reading.</summary>
|
||||
<remarks>To be added.</remarks>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
<Members>
|
||||
<Member MemberName="Reading">
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<Type Name="Geolocation" FullName="Xamarin.Essentials.Geolocation">
|
||||
<TypeSignature Language="C#" Value="public static class Geolocation" />
|
||||
<TypeSignature Language="ILAsm" Value=".class public auto ansi abstract sealed beforefieldinit Geolocation extends System.Object" />
|
||||
<AssemblyInfo>
|
||||
<AssemblyName>Xamarin.Essentials</AssemblyName>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Base>
|
||||
<BaseTypeName>System.Object</BaseTypeName>
|
||||
</Base>
|
||||
<Interfaces />
|
||||
<Docs>
|
||||
<summary>Provides a way to get the current location of the device.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
<Members>
|
||||
<Member MemberName="GetLastKnownLocationAsync">
|
||||
<MemberSignature Language="C#" Value="public static System.Threading.Tasks.Task<Xamarin.Essentials.Location> GetLastKnownLocationAsync ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1<class Xamarin.Essentials.Location> GetLastKnownLocationAsync() cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Threading.Tasks.Task<Xamarin.Essentials.Location></ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>Returns the last known location of the device.</summary>
|
||||
<returns>Returns the location.</returns>
|
||||
<remarks>This location may be a recently cached location.</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="GetLocationAsync">
|
||||
<MemberSignature Language="C#" Value="public static System.Threading.Tasks.Task<Xamarin.Essentials.Location> GetLocationAsync ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1<class Xamarin.Essentials.Location> GetLocationAsync() cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Threading.Tasks.Task<Xamarin.Essentials.Location></ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>Returns the current location of the device.</summary>
|
||||
<returns>Returns the location.</returns>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="GetLocationAsync">
|
||||
<MemberSignature Language="C#" Value="public static System.Threading.Tasks.Task<Xamarin.Essentials.Location> GetLocationAsync (Xamarin.Essentials.GeolocationRequest request);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1<class Xamarin.Essentials.Location> GetLocationAsync(class Xamarin.Essentials.GeolocationRequest request) cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Threading.Tasks.Task<Xamarin.Essentials.Location></ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters>
|
||||
<Parameter Name="request" Type="Xamarin.Essentials.GeolocationRequest" />
|
||||
</Parameters>
|
||||
<Docs>
|
||||
<param name="request">The criteria to use when determining the location of the device.</param>
|
||||
<summary>Returns the current location of the device using the specified criteria.</summary>
|
||||
<returns>Returns the location.</returns>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="GetLocationAsync">
|
||||
<MemberSignature Language="C#" Value="public static System.Threading.Tasks.Task<Xamarin.Essentials.Location> GetLocationAsync (Xamarin.Essentials.GeolocationRequest request, System.Threading.CancellationToken cancelToken);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig class System.Threading.Tasks.Task`1<class Xamarin.Essentials.Location> GetLocationAsync(class Xamarin.Essentials.GeolocationRequest request, valuetype System.Threading.CancellationToken cancelToken) cil managed" />
|
||||
<MemberType>Method</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Threading.Tasks.Task<Xamarin.Essentials.Location></ReturnType>
|
||||
</ReturnValue>
|
||||
<Parameters>
|
||||
<Parameter Name="request" Type="Xamarin.Essentials.GeolocationRequest" />
|
||||
<Parameter Name="cancelToken" Type="System.Threading.CancellationToken" />
|
||||
</Parameters>
|
||||
<Docs>
|
||||
<param name="request">The criteria to use when determining the location of the device.</param>
|
||||
<param name="cancelToken">A token for cancelling the operation.</param>
|
||||
<summary>Returns the current location of the device using the specified criteria.</summary>
|
||||
<returns>Returns the location.</returns>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
</Members>
|
||||
</Type>
|
|
@ -0,0 +1,92 @@
|
|||
<Type Name="GeolocationAccuracy" FullName="Xamarin.Essentials.GeolocationAccuracy">
|
||||
<TypeSignature Language="C#" Value="public enum GeolocationAccuracy" />
|
||||
<TypeSignature Language="ILAsm" Value=".class public auto ansi sealed GeolocationAccuracy extends System.Enum" />
|
||||
<AssemblyInfo>
|
||||
<AssemblyName>Xamarin.Essentials</AssemblyName>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Base>
|
||||
<BaseTypeName>System.Enum</BaseTypeName>
|
||||
</Base>
|
||||
<Docs>
|
||||
<summary>Represents levels of accuracy when determining location.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
<Members>
|
||||
<Member MemberName="Best">
|
||||
<MemberSignature Language="C#" Value="Best" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Essentials.GeolocationAccuracy Best = int32(4)" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<MemberValue>4</MemberValue>
|
||||
<Docs>
|
||||
<summary>Represents the best accuracy, using the most power to obtain and typically within 10 meters.</summary>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="High">
|
||||
<MemberSignature Language="C#" Value="High" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Essentials.GeolocationAccuracy High = int32(3)" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<MemberValue>3</MemberValue>
|
||||
<Docs>
|
||||
<summary>Represents high accuracy, typically within 10-100 meters.</summary>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Low">
|
||||
<MemberSignature Language="C#" Value="Low" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Essentials.GeolocationAccuracy Low = int32(1)" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<MemberValue>1</MemberValue>
|
||||
<Docs>
|
||||
<summary>Represents low accuracy, typically within 300-3000 meters.</summary>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Lowest">
|
||||
<MemberSignature Language="C#" Value="Lowest" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Essentials.GeolocationAccuracy Lowest = int32(0)" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<MemberValue>0</MemberValue>
|
||||
<Docs>
|
||||
<summary>Represents the lowest accuracy, using the least power to obtain and typically within 1000-5000 meters.</summary>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Medium">
|
||||
<MemberSignature Language="C#" Value="Medium" />
|
||||
<MemberSignature Language="ILAsm" Value=".field public static literal valuetype Xamarin.Essentials.GeolocationAccuracy Medium = int32(2)" />
|
||||
<MemberType>Field</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<MemberValue>2</MemberValue>
|
||||
<Docs>
|
||||
<summary>Represents medium accuracy, typically within 30-500 meters.</summary>
|
||||
</Docs>
|
||||
</Member>
|
||||
</Members>
|
||||
</Type>
|
|
@ -0,0 +1,97 @@
|
|||
<Type Name="GeolocationRequest" FullName="Xamarin.Essentials.GeolocationRequest">
|
||||
<TypeSignature Language="C#" Value="public class GeolocationRequest" />
|
||||
<TypeSignature Language="ILAsm" Value=".class public auto ansi beforefieldinit GeolocationRequest extends System.Object" />
|
||||
<AssemblyInfo>
|
||||
<AssemblyName>Xamarin.Essentials</AssemblyName>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Base>
|
||||
<BaseTypeName>System.Object</BaseTypeName>
|
||||
</Base>
|
||||
<Interfaces />
|
||||
<Docs>
|
||||
<summary>Represents the criteria for a location request.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
<Members>
|
||||
<Member MemberName=".ctor">
|
||||
<MemberSignature Language="C#" Value="public GeolocationRequest ();" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig specialname rtspecialname instance void .ctor() cil managed" />
|
||||
<MemberType>Constructor</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Parameters />
|
||||
<Docs>
|
||||
<summary>Creates a new instance of GeolocationRequest.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName=".ctor">
|
||||
<MemberSignature Language="C#" Value="public GeolocationRequest (Xamarin.Essentials.GeolocationAccuracy accuracy);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig specialname rtspecialname instance void .ctor(valuetype Xamarin.Essentials.GeolocationAccuracy accuracy) cil managed" />
|
||||
<MemberType>Constructor</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Parameters>
|
||||
<Parameter Name="accuracy" Type="Xamarin.Essentials.GeolocationAccuracy" />
|
||||
</Parameters>
|
||||
<Docs>
|
||||
<param name="accuracy">The desired accuracy.</param>
|
||||
<summary>Creates a new instance of GeolocationRequest with the specified accuracy.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName=".ctor">
|
||||
<MemberSignature Language="C#" Value="public GeolocationRequest (Xamarin.Essentials.GeolocationAccuracy accuracy, TimeSpan timeout);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public hidebysig specialname rtspecialname instance void .ctor(valuetype Xamarin.Essentials.GeolocationAccuracy accuracy, valuetype System.TimeSpan timeout) cil managed" />
|
||||
<MemberType>Constructor</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<Parameters>
|
||||
<Parameter Name="accuracy" Type="Xamarin.Essentials.GeolocationAccuracy" />
|
||||
<Parameter Name="timeout" Type="System.TimeSpan" />
|
||||
</Parameters>
|
||||
<Docs>
|
||||
<param name="accuracy">The desired accuracy.</param>
|
||||
<param name="timeout">The request timeout.</param>
|
||||
<summary>Creates a new instance of GeolocationRequest with the specified accuracy and timeout.</summary>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="DesiredAccuracy">
|
||||
<MemberSignature Language="C#" Value="public Xamarin.Essentials.GeolocationAccuracy DesiredAccuracy { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance valuetype Xamarin.Essentials.GeolocationAccuracy DesiredAccuracy" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>Xamarin.Essentials.GeolocationAccuracy</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>Gets or sets the desired accuracy of the resulting location.</summary>
|
||||
<value>The desired accuracy of the location.</value>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Timeout">
|
||||
<MemberSignature Language="C#" Value="public TimeSpan Timeout { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance valuetype System.TimeSpan Timeout" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.TimeSpan</ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>Gets or sets the location request timeout.</summary>
|
||||
<value>The location request timeout.</value>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
</Members>
|
||||
</Type>
|
|
@ -100,6 +100,22 @@
|
|||
</remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="Accuracy">
|
||||
<MemberSignature Language="C#" Value="public Nullable<double> Accuracy { get; set; }" />
|
||||
<MemberSignature Language="ILAsm" Value=".property instance valuetype System.Nullable`1<float64> Accuracy" />
|
||||
<MemberType>Property</MemberType>
|
||||
<AssemblyInfo>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
</AssemblyInfo>
|
||||
<ReturnValue>
|
||||
<ReturnType>System.Nullable<System.Double></ReturnType>
|
||||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>Gets or sets the accuracy (in meters) of the location.</summary>
|
||||
<value>The location accuracy.</value>
|
||||
<remarks></remarks>
|
||||
</Docs>
|
||||
</Member>
|
||||
<Member MemberName="CalculateDistance">
|
||||
<MemberSignature Language="C#" Value="public static double CalculateDistance (Xamarin.Essentials.Location locationStart, Xamarin.Essentials.Location locationEnd, Xamarin.Essentials.DistanceUnits units);" />
|
||||
<MemberSignature Language="ILAsm" Value=".method public static hidebysig float64 CalculateDistance(class Xamarin.Essentials.Location locationStart, class Xamarin.Essentials.Location locationEnd, valuetype Xamarin.Essentials.DistanceUnits units) cil managed" />
|
||||
|
@ -264,7 +280,7 @@
|
|||
</ReturnValue>
|
||||
<Docs>
|
||||
<summary>Gets or sets the timestamp of the location.</summary>
|
||||
<value>Utc timestamp.</value>
|
||||
<value>UTC timestamp.</value>
|
||||
<remarks>
|
||||
<para></para>
|
||||
</remarks>
|
||||
|
|
|
@ -56,8 +56,8 @@
|
|||
</Attributes>
|
||||
</Assembly>
|
||||
</Assemblies>
|
||||
<Remarks>To be added.</Remarks>
|
||||
<Copyright>To be added.</Copyright>
|
||||
<Remarks></Remarks>
|
||||
<Copyright>© Microsoft Corporation. All rights reserved.</Copyright>
|
||||
<Types>
|
||||
<Namespace Name="Xamarin.Essentials">
|
||||
<Type Name="Accelerometer" Kind="Class" />
|
||||
|
@ -94,6 +94,9 @@
|
|||
<Type Name="FileSystem" Kind="Class" />
|
||||
<Type Name="Flashlight" Kind="Class" />
|
||||
<Type Name="Geocoding" Kind="Class" />
|
||||
<Type Name="Geolocation" Kind="Class" />
|
||||
<Type Name="GeolocationAccuracy" Kind="Enumeration" />
|
||||
<Type Name="GeolocationRequest" Kind="Class" />
|
||||
<Type Name="Gyroscope" Kind="Class" />
|
||||
<Type Name="GyroscopeChangedEventArgs" Kind="Class" />
|
||||
<Type Name="GyroscopeChangedEventHandler" Kind="Delegate" />
|
||||
|
|
Загрузка…
Ссылка в новой задаче