Skip to content
This repository was archived by the owner on May 15, 2024. It is now read-only.

Commit f185ef4

Browse files
Redthjamesmontemagno
authored andcommitted
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
1 parent f2420c8 commit f185ef4

36 files changed

+1140
-85
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<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">
33
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
4+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
5+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
46
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
57
<uses-permission android:name="android.permission.BATTERY_STATS" />
8+
<uses-permission android:name="android.permission.CAMERA" />
9+
<uses-permission android:name="android.permission.FLASHLIGHT" />
10+
<uses-permission android:name="android.permission.INTERNET" />
11+
<uses-permission android:name="android.permission.VIBRATE" />
612
<application android:label="@string/app_name" android:icon="@drawable/icon" android:theme="@style/MainTheme"></application>
713
</manifest>

DeviceTests/DeviceTests.Android/Properties/AssemblyInfo.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,3 @@
2828
// [assembly: AssemblyVersion("1.0.*")]
2929
[assembly: AssemblyVersion("1.0.0.0")]
3030
[assembly: AssemblyFileVersion("1.0.0.0")]
31-
32-
// Add some common permissions, these can be removed if not needed
33-
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
34-
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

DeviceTests/DeviceTests.Shared/DeviceTests.Shared.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
<Compile Include="$(MSBuildThisFileDirectory)DeviceInfo_Tests.cs" />
2121
<Compile Include="$(MSBuildThisFileDirectory)Flashlight_Tests.cs" />
2222
<Compile Include="$(MSBuildThisFileDirectory)Geocoding_Tests.cs" />
23+
<Compile Include="$(MSBuildThisFileDirectory)Geolocation_Tests.cs" />
2324
<Compile Include="$(MSBuildThisFileDirectory)Permissions_Tests.cs" />
2425
<Compile Include="$(MSBuildThisFileDirectory)PhoneDialer_Tests.cs" />
2526
<Compile Include="$(MSBuildThisFileDirectory)ScreenLock_Tests.cs" />
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using Xamarin.Essentials;
5+
using Xunit;
6+
7+
namespace DeviceTests
8+
{
9+
// TEST NOTES:
10+
// - a human needs to accept permissions
11+
public class Geolocation_Tests
12+
{
13+
[Fact]
14+
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
15+
public async Task Get_LastKnownLocation_Is_Something()
16+
{
17+
var location = await Geolocation.GetLastKnownLocationAsync();
18+
19+
Assert.NotNull(location);
20+
21+
Assert.True(location.Accuracy > 0);
22+
Assert.NotEqual(0.0, location.Latitude);
23+
Assert.NotEqual(0.0, location.Longitude);
24+
25+
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
26+
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
27+
28+
// before right now, but after yesterday
29+
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
30+
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
31+
}
32+
33+
[Fact]
34+
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
35+
public async Task Get_Location_Is_Something()
36+
{
37+
var location = await Geolocation.GetLocationAsync();
38+
39+
Assert.NotNull(location);
40+
41+
Assert.True(location.Accuracy > 0);
42+
Assert.NotEqual(0.0, location.Latitude);
43+
Assert.NotEqual(0.0, location.Longitude);
44+
45+
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
46+
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
47+
48+
// before right now, but after yesterday
49+
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
50+
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
51+
}
52+
53+
[Fact]
54+
[Trait(Traits.InteractionType, Traits.InteractionTypes.Human)]
55+
public async Task Get_Location_With_Request_Is_Something()
56+
{
57+
var request = new GeolocationRequest(GeolocationAccuracy.Best);
58+
var location = await Geolocation.GetLocationAsync(request);
59+
60+
Assert.NotNull(location);
61+
62+
Assert.True(location.Accuracy > 0);
63+
Assert.NotEqual(0.0, location.Latitude);
64+
Assert.NotEqual(0.0, location.Longitude);
65+
66+
Assert.NotEqual(DateTimeOffset.MaxValue, location.TimestampUtc);
67+
Assert.NotEqual(DateTimeOffset.MinValue, location.TimestampUtc);
68+
69+
// before right now, but after yesterday
70+
Assert.True(location.TimestampUtc < DateTimeOffset.UtcNow);
71+
Assert.True(location.TimestampUtc > DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(1)));
72+
}
73+
}
74+
}

DeviceTests/DeviceTests.Shared/Permissions_Tests.cs

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,19 @@
55
using Xamarin.Essentials;
66
using Xunit;
77

8-
#if __ANDROID__
9-
[assembly: Android.App.UsesPermission(Android.Manifest.Permission.BatteryStats)]
10-
#endif
11-
128
namespace DeviceTests
139
{
1410
public class Permissions_Tests
1511
{
1612
[Theory]
1713
[InlineData(PermissionType.Battery)]
1814
[InlineData(PermissionType.NetworkState)]
15+
[InlineData(PermissionType.LocationWhenInUse)]
1916
internal void Ensure_Declared(PermissionType permission)
2017
{
2118
Permissions.EnsureDeclared(permission);
2219
}
2320

24-
[Theory]
25-
[InlineData(PermissionType.LocationWhenInUse)]
26-
internal void Ensure_Declared_Throws(PermissionType permission)
27-
{
28-
if (DeviceInfo.Platform == DeviceInfo.Platforms.UWP)
29-
{
30-
return;
31-
}
32-
33-
Assert.Throws<PermissionException>(() => Permissions.EnsureDeclared(permission));
34-
}
35-
3621
[Theory]
3722
[InlineData(PermissionType.Battery, PermissionStatus.Granted)]
3823
[InlineData(PermissionType.NetworkState, PermissionStatus.Granted)]
@@ -43,18 +28,6 @@ internal async Task Check_Status(PermissionType permission, PermissionStatus exp
4328
Assert.Equal(expectedStatus, status);
4429
}
4530

46-
[Theory]
47-
[InlineData(PermissionType.LocationWhenInUse)]
48-
internal Task Check_Status_Throws(PermissionType permission)
49-
{
50-
if (DeviceInfo.Platform == DeviceInfo.Platforms.UWP)
51-
{
52-
return Task.CompletedTask;
53-
}
54-
55-
return Assert.ThrowsAsync<PermissionException>(async () => await Permissions.CheckStatusAsync(permission));
56-
}
57-
5831
[Theory]
5932
[InlineData(PermissionType.Battery, PermissionStatus.Granted)]
6033
[InlineData(PermissionType.NetworkState, PermissionStatus.Granted)]

DeviceTests/DeviceTests.iOS/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,7 @@
5353
<string>DeviceTests</string>
5454
<key>CFBundleShortVersionString</key>
5555
<string>1.0.1.0</string>
56+
<key>NSLocationWhenInUseUsageDescription</key>
57+
<string>Access to your location is required for cool things to happen!</string>
5658
</dict>
5759
</plist>
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.xamarin.essentials" android:installLocation="auto">
33
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="26" />
4+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
5+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
46
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
57
<uses-permission android:name="android.permission.BATTERY_STATS" />
6-
<uses-permission android:name="android.permission.VIBRATE" />
78
<uses-permission android:name="android.permission.CAMERA" />
89
<uses-permission android:name="android.permission.FLASHLIGHT" />
10+
<uses-permission android:name="android.permission.INTERNET" />
11+
<uses-permission android:name="android.permission.VIBRATE" />
912
<application android:label="@string/app_name" android:icon="@drawable/icon" android:theme="@style/MainTheme"></application>
1013
</manifest>

Samples/Samples.Android/Properties/AssemblyInfo.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,3 @@
2727
// [assembly: AssemblyVersion("1.0.*")]
2828
[assembly: AssemblyVersion("1.0.0.0")]
2929
[assembly: AssemblyFileVersion("1.0.0.0")]
30-
31-
// Add some common permissions, these can be removed if not needed
32-
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
33-
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

Samples/Samples.UWP/Package.appxmanifest

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@
2424
</Applications>
2525
<Capabilities>
2626
<Capability Name="internetClient" />
27+
<DeviceCapability Name="location" />
2728
</Capabilities>
2829
</Package>

Samples/Samples.iOS/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,7 @@
5353
<string>Xamarin.Essentials</string>
5454
<key>CFBundleShortVersionString</key>
5555
<string>1.0</string>
56+
<key>NSLocationWhenInUseUsageDescription</key>
57+
<string>Access to your location is required for cool things to happen!</string>
5658
</dict>
5759
</plist>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<views:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:views="clr-namespace:Samples.View"
5+
xmlns:viewmodels="clr-namespace:Samples.ViewModel"
6+
x:Class="Samples.View.GeolocationPage"
7+
Title="Geolocation">
8+
<ContentPage.BindingContext>
9+
<viewmodels:GeolocationViewModel />
10+
</ContentPage.BindingContext>
11+
12+
<StackLayout>
13+
<Label Text="Quickly get the current location." FontAttributes="Bold" Margin="12" />
14+
15+
<ScrollView>
16+
<StackLayout Padding="12,0,12,12" Spacing="6">
17+
<ActivityIndicator IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" />
18+
19+
<Label Text="Last Known Location:" FontAttributes="Bold" Margin="0,6,0,0" />
20+
<Label Text="{Binding LastLocation}" />
21+
<Button Text="Refresh" Command="{Binding GetLastLocationCommand}" IsEnabled="{Binding IsNotBusy}" />
22+
23+
<Label Text="Current Location:" FontAttributes="Bold" Margin="0,6,0,0" />
24+
<Label Text="{Binding CurrentLocation}" />
25+
<Label Text="Accuracy:" />
26+
<Picker ItemsSource="{Binding Accuracies}"
27+
SelectedIndex="{Binding Accuracy, Mode=TwoWay}"
28+
IsEnabled="{Binding IsNotBusy}"
29+
HorizontalOptions="FillAndExpand" />
30+
<Button Text="Refresh" Command="{Binding GetCurrentLocationCommand}" IsEnabled="{Binding IsNotBusy}" />
31+
</StackLayout>
32+
</ScrollView>
33+
</StackLayout>
34+
35+
</views:BasePage>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Samples.View
2+
{
3+
public partial class GeolocationPage : BasePage
4+
{
5+
public GeolocationPage()
6+
{
7+
InitializeComponent();
8+
}
9+
}
10+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System;
2+
using System.Windows.Input;
3+
using Xamarin.Essentials;
4+
using Xamarin.Forms;
5+
6+
namespace Samples.ViewModel
7+
{
8+
public class GeolocationViewModel : BaseViewModel
9+
{
10+
string lastLocation;
11+
string currentLocation;
12+
int accuracy = (int)GeolocationAccuracy.Medium;
13+
14+
public GeolocationViewModel()
15+
{
16+
GetLastLocationCommand = new Command(OnGetLastLocation);
17+
GetCurrentLocationCommand = new Command(OnGetCurrentLocation);
18+
}
19+
20+
public ICommand GetLastLocationCommand { get; }
21+
22+
public ICommand GetCurrentLocationCommand { get; }
23+
24+
public string LastLocation
25+
{
26+
get => lastLocation;
27+
set => SetProperty(ref lastLocation, value);
28+
}
29+
30+
public string CurrentLocation
31+
{
32+
get => currentLocation;
33+
set => SetProperty(ref currentLocation, value);
34+
}
35+
36+
public string[] Accuracies
37+
=> Enum.GetNames(typeof(GeolocationAccuracy));
38+
39+
public int Accuracy
40+
{
41+
get => accuracy;
42+
set => SetProperty(ref accuracy, value);
43+
}
44+
45+
async void OnGetLastLocation()
46+
{
47+
if (IsBusy)
48+
return;
49+
50+
IsBusy = true;
51+
try
52+
{
53+
var location = await Geolocation.GetLastKnownLocationAsync();
54+
LastLocation = FormatLocation(location);
55+
}
56+
catch (Exception)
57+
{
58+
LastLocation = FormatLocation(null);
59+
}
60+
IsBusy = false;
61+
}
62+
63+
async void OnGetCurrentLocation()
64+
{
65+
if (IsBusy)
66+
return;
67+
68+
IsBusy = true;
69+
try
70+
{
71+
var request = new GeolocationRequest((GeolocationAccuracy)Accuracy);
72+
var location = await Geolocation.GetLocationAsync(request);
73+
CurrentLocation = FormatLocation(location);
74+
}
75+
catch (Exception)
76+
{
77+
CurrentLocation = FormatLocation(null);
78+
}
79+
IsBusy = false;
80+
}
81+
82+
private string FormatLocation(Location location)
83+
{
84+
if (location == null)
85+
{
86+
return "Unable to detect location.";
87+
}
88+
89+
return
90+
$"Latitude: {location.Latitude}\n" +
91+
$"Longitude: {location.Longitude}\n" +
92+
$"Accuracy: {location.Accuracy}\n" +
93+
$"Date (UTC): {location.TimestampUtc:d}\n" +
94+
$"Time (UTC): {location.TimestampUtc:T}";
95+
}
96+
}
97+
}

Samples/Samples/ViewModel/HomeViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public HomeViewModel()
2727
new SampleItem("File System", typeof(FileSystemPage), "Easily save files to app data."),
2828
new SampleItem("Flashlight", typeof(FlashlightPage), "A simple way to turn the flashlight on/off."),
2929
new SampleItem("Geocoding", typeof(GeocodingPage), "Easily geocode and reverse geocoding."),
30+
new SampleItem("Geolocation", typeof(GeolocationPage), "Quickly get the current location."),
3031
new SampleItem("Gyroscope", typeof(GyroscopePage), "Retrieve rotation around the device's three primary axes."),
3132
new SampleItem("Magnetometer", typeof(MagnetometerPage), "Detect device's orientation relative to Earth's magnetic field."),
3233
new SampleItem("Phone Dialer", typeof(PhoneDialerPage), "Easily open phone dialer."),

0 commit comments

Comments
 (0)