Skip to content

Commit

Permalink
Geolocation: Add #nullable for Essentials Geolocation code (dotnet#13371
Browse files Browse the repository at this point in the history
)

* enabled nullable checks for GeolocationRequest class

* enabled nullable checks for more Geolocation source files

* enabled nullable checks for Geolocation Android and iOS implementation

* changed null checks as suggested in review

* fixed nullable errors for Tizen

* Fix some forgotte == null

* Fix some forgotte != null

---------

Co-authored-by: Manuel de la Pena <mandel@microsoft.com>
  • Loading branch information
2 people authored and TJ Lambert committed Feb 21, 2023
1 parent baa0a45 commit 6a4c073
Show file tree
Hide file tree
Showing 14 changed files with 82 additions and 48 deletions.
82 changes: 50 additions & 32 deletions src/Essentials/src/Geolocation/Geolocation.android.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
Expand All @@ -18,40 +19,46 @@ partial class GeolocationImplementation : IGeolocation
const long twoMinutes = 120000;
static readonly string[] ignoredProviders = new string[] { LocationManager.PassiveProvider, "local_database" };

static ContinuousLocationListener continuousListener;
static List<string> listeningProviders;
static ContinuousLocationListener? continuousListener;
static List<string>? listeningProviders;

static LocationManager locationManager;
static LocationManager? locationManager;

static LocationManager LocationManager =>
static LocationManager? LocationManager =>
locationManager ??= Application.Context.GetSystemService(Context.LocationService) as LocationManager;

/// <summary>
/// Indicates if currently listening to location updates while the app is in foreground.
/// </summary>
public bool IsListeningForeground { get => continuousListener != null; }
public bool IsListeningForeground { get => continuousListener is not null; }

public async Task<Location> GetLastKnownLocationAsync()
public async Task<Location?> GetLastKnownLocationAsync()
{
if (LocationManager is null)
throw new FeatureNotSupportedException("Android LocationManager is not available");

await Permissions.EnsureGrantedOrRestrictedAsync<Permissions.LocationWhenInUse>();

AndroidLocation bestLocation = null;
AndroidLocation? bestLocation = null;

foreach (var provider in LocationManager.GetProviders(true))
{
var location = LocationManager.GetLastKnownLocation(provider);

if (location != null && IsBetterLocation(location, bestLocation))
if (location is not null && IsBetterLocation(location, bestLocation))
bestLocation = location;
}

return bestLocation?.ToLocation();
}

public async Task<Location> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
public async Task<Location?> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);

if (LocationManager is null)
throw new FeatureNotSupportedException("Android LocationManager is not available");

await Permissions.EnsureGrantedOrRestrictedAsync<Permissions.LocationWhenInUse>();

var enabledProviders = LocationManager.GetProviders(true);
Expand All @@ -68,7 +75,7 @@ public async Task<Location> GetLocationAsync(GeolocationRequest request, Cancell
if (string.IsNullOrEmpty(providerInfo.Provider))
return await GetLastKnownLocationAsync();

var tcs = new TaskCompletionSource<AndroidLocation>();
var tcs = new TaskCompletionSource<AndroidLocation?>();

var allProviders = LocationManager.GetProviders(false);

Expand Down Expand Up @@ -96,7 +103,7 @@ public async Task<Location> GetLocationAsync(GeolocationRequest request, Cancell

var androidLocation = await tcs.Task;

if (androidLocation == null)
if (androidLocation is null)
return null;

return androidLocation.ToLocation();
Expand All @@ -110,11 +117,14 @@ void HandleLocation(AndroidLocation location)
void Cancel()
{
RemoveUpdates();
tcs.TrySetResult(listener.BestLocation);
tcs.TrySetResult(listener?.BestLocation);
}

void RemoveUpdates()
{
if (LocationManager is null)
return;

for (var i = 0; i < providers.Count; i++)
LocationManager.RemoveUpdates(listener);
}
Expand All @@ -135,13 +145,17 @@ public async Task<bool> StartListeningForegroundAsync(GeolocationListeningReques
{
ArgumentNullException.ThrowIfNull(request);

if (LocationManager is null)
throw new FeatureNotSupportedException("Android LocationManager is not available");

if (IsListeningForeground)
throw new InvalidOperationException("Already listening to location changes.");

await Permissions.EnsureGrantedOrRestrictedAsync<Permissions.LocationWhenInUse>();

var enabledProviders = LocationManager.GetProviders(true);
var hasProviders = enabledProviders.Any(p => !ignoredProviders.Contains(p));
var hasProviders = enabledProviders is not null &&
enabledProviders.Any(p => !ignoredProviders.Contains(p));

if (!hasProviders)
throw new FeatureNotEnabledException("Location services are not enabled on device.");
Expand Down Expand Up @@ -198,13 +212,14 @@ void HandleError(GeolocationError geolocationError)
/// </summary>
public void StopListeningForeground()
{
if (continuousListener == null)
if (continuousListener is null)
return;

continuousListener.LocationHandler = null;
continuousListener.ErrorHandler = null;

if (listeningProviders == null)
if (listeningProviders is null ||
LocationManager is null)
return;

for (var i = 0; i < listeningProviders.Count; i++)
Expand All @@ -215,7 +230,7 @@ public void StopListeningForeground()
continuousListener = null;
}

static (string Provider, float Accuracy) GetBestProvider(LocationManager locationManager, GeolocationAccuracy accuracy)
static (string? Provider, float Accuracy) GetBestProvider(LocationManager locationManager, GeolocationAccuracy accuracy)
{
// Criteria: https://developer.android.com/reference/android/location/Criteria

Expand Down Expand Up @@ -268,9 +283,9 @@ public void StopListeningForeground()
return (provider, accuracyDistance);
}

internal static bool IsBetterLocation(AndroidLocation location, AndroidLocation bestLocation)
internal static bool IsBetterLocation(AndroidLocation location, AndroidLocation? bestLocation)
{
if (bestLocation == null)
if (bestLocation is null)
return true;

var timeDelta = location.Time - bestLocation.Time;
Expand Down Expand Up @@ -311,24 +326,24 @@ class SingleLocationListener : Java.Lang.Object, ILocationListener

float desiredAccuracy;

internal AndroidLocation BestLocation { get; set; }
internal AndroidLocation? BestLocation { get; set; }

HashSet<string> activeProviders = new HashSet<string>();

bool wasRaised = false;

internal Action<AndroidLocation> LocationHandler { get; set; }
internal Action<AndroidLocation>? LocationHandler { get; set; }

internal SingleLocationListener(LocationManager manager, float desiredAccuracy, IEnumerable<string> activeProviders)
internal SingleLocationListener(LocationManager? manager, float desiredAccuracy, IEnumerable<string> activeProviders)
{
this.desiredAccuracy = desiredAccuracy;

this.activeProviders = new HashSet<string>(activeProviders);

foreach (var provider in activeProviders)
{
var location = manager.GetLastKnownLocation(provider);
if (location != null && GeolocationImplementation.IsBetterLocation(location, BestLocation))
var location = manager?.GetLastKnownLocation(provider);
if (location is not null && GeolocationImplementation.IsBetterLocation(location, BestLocation))
BestLocation = location;
}
}
Expand Down Expand Up @@ -365,8 +380,11 @@ void ILocationListener.OnProviderEnabled(string provider)
activeProviders.Add(provider);
}

void ILocationListener.OnStatusChanged(string provider, Availability status, Bundle extras)
void ILocationListener.OnStatusChanged(string? provider, Availability status, Bundle? extras)
{
if (provider is null)
return;

switch (status)
{
case Availability.Available:
Expand All @@ -382,24 +400,21 @@ void ILocationListener.OnStatusChanged(string provider, Availability status, Bun

class ContinuousLocationListener : Java.Lang.Object, ILocationListener
{
readonly LocationManager manager;

float desiredAccuracy;

HashSet<string> activeProviders = new HashSet<string>();

internal Action<AndroidLocation> LocationHandler { get; set; }
internal Action<AndroidLocation>? LocationHandler { get; set; }

internal Action<GeolocationError> ErrorHandler { get; set; }
internal Action<GeolocationError>? ErrorHandler { get; set; }

internal ContinuousLocationListener(LocationManager manager, float desiredAccuracy, IEnumerable<string> providers)
internal ContinuousLocationListener(LocationManager? manager, float desiredAccuracy, IEnumerable<string> providers)
{
this.manager = manager;
this.desiredAccuracy = desiredAccuracy;

foreach (var provider in providers)
{
if (manager.IsProviderEnabled(provider))
if (manager is not null && manager.IsProviderEnabled(provider))
activeProviders.Add(provider);
}
}
Expand Down Expand Up @@ -429,8 +444,11 @@ void ILocationListener.OnProviderEnabled(string provider)
activeProviders.Add(provider);
}

void ILocationListener.OnStatusChanged(string provider, Availability status, Bundle extras)
void ILocationListener.OnStatusChanged(string? provider, Availability status, Bundle? extras)
{
if (provider is null)
return;

switch (status)
{
case Availability.Available:
Expand Down
22 changes: 12 additions & 10 deletions src/Essentials/src/Geolocation/Geolocation.ios.macos.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Linq;
using System.Threading;
Expand All @@ -10,14 +11,14 @@ namespace Microsoft.Maui.Devices.Sensors
{
partial class GeolocationImplementation : IGeolocation
{
CLLocationManager listeningManager;
CLLocationManager? listeningManager;

/// <summary>
/// Indicates if currently listening to location updates while the app is in foreground.
/// </summary>
public bool IsListeningForeground { get => listeningManager != null; }

public async Task<Location> GetLastKnownLocationAsync()
public async Task<Location?> GetLastKnownLocationAsync()
{
if (!CLLocationManager.LocationServicesEnabled)
throw new FeatureNotEnabledException("Location services are not enabled on device.");
Expand All @@ -37,7 +38,7 @@ public async Task<Location> GetLastKnownLocationAsync()
return location?.ToLocation(reducedAccuracy);
}

public async Task<Location> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
public async Task<Location?> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);

Expand All @@ -50,7 +51,7 @@ public async Task<Location> GetLocationAsync(GeolocationRequest request, Cancell
// so just use the main loop
var manager = MainThread.InvokeOnMainThread(() => new CLLocationManager());

var tcs = new TaskCompletionSource<CLLocation>(manager);
var tcs = new TaskCompletionSource<CLLocation?>(manager);

var listener = new SingleLocationListener();
listener.LocationHandler += HandleLocation;
Expand Down Expand Up @@ -155,7 +156,7 @@ public async Task<bool> StartListeningForegroundAsync(GeolocationListeningReques

void HandleLocation(CLLocation clLocation)
{
OnLocationChanged(clLocation?.ToLocation(reducedAccuracy));
OnLocationChanged(clLocation.ToLocation(reducedAccuracy));
}

void HandleError(GeolocationError error)
Expand All @@ -172,7 +173,8 @@ void HandleError(GeolocationError error)
/// </summary>
public void StopListeningForeground()
{
if (!IsListeningForeground)
if (!IsListeningForeground ||
listeningManager is null)
return;

listeningManager.StopUpdatingLocation();
Expand All @@ -183,7 +185,7 @@ public void StopListeningForeground()
listener.ErrorHandler = null;
}

listeningManager.Delegate = null;
listeningManager.WeakDelegate = null;

listeningManager = null;
}
Expand All @@ -193,7 +195,7 @@ class SingleLocationListener : CLLocationManagerDelegate
{
bool wasRaised = false;

internal Action<CLLocation> LocationHandler { get; set; }
internal Action<CLLocation>? LocationHandler { get; set; }

/// <inheritdoc/>
public override void LocationsUpdated(CLLocationManager manager, CLLocation[] locations)
Expand All @@ -217,9 +219,9 @@ public override void LocationsUpdated(CLLocationManager manager, CLLocation[] lo

class ContinuousLocationListener : CLLocationManagerDelegate
{
internal Action<CLLocation> LocationHandler { get; set; }
internal Action<CLLocation>? LocationHandler { get; set; }

internal Action<GeolocationError> ErrorHandler { get; set; }
internal Action<GeolocationError>? ErrorHandler { get; set; }

/// <inheritdoc/>
public override void LocationsUpdated(CLLocationManager manager, CLLocation[] locations)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -7,10 +8,10 @@ namespace Microsoft.Maui.Devices.Sensors
{
partial class GeolocationImplementation : IGeolocation
{
public Task<Location> GetLastKnownLocationAsync() =>
public Task<Location?> GetLastKnownLocationAsync() =>
throw ExceptionUtils.NotSupportedOrImplementedException;

public Task<Location> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken) =>
public Task<Location?> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken) =>
throw ExceptionUtils.NotSupportedOrImplementedException;

public bool IsListeningForeground { get => false; }
Expand Down
7 changes: 4 additions & 3 deletions src/Essentials/src/Geolocation/Geolocation.tizen.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -12,15 +13,15 @@ partial class GeolocationImplementation : IGeolocation

public bool IsListeningForeground { get => false; }

public Task<Location> GetLastKnownLocationAsync() => Task.FromResult(lastKnownLocation);
public Task<Location?> GetLastKnownLocationAsync() => Task.FromResult<Location?>(lastKnownLocation);

public async Task<Location> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
public async Task<Location?> GetLocationAsync(GeolocationRequest request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);

await Permissions.EnsureGrantedAsync<Permissions.LocationWhenInUse>();

Locator service = null;
Locator? service = null;
var gps = PlatformUtils.GetFeatureInfo<bool>("location.gps");
var wps = PlatformUtils.GetFeatureInfo<bool>("location.wps");
if (gps)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Microsoft.Maui.Devices.Sensors
#nullable enable

namespace Microsoft.Maui.Devices.Sensors
{
static class GeolocationAccuracyExtensionMethods
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#nullable enable
using System;

namespace Microsoft.Maui.Devices.Sensors
Expand Down
2 changes: 2 additions & 0 deletions src/Essentials/src/Geolocation/GeolocationRequest.uwp.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#nullable enable

namespace Microsoft.Maui.Devices.Sensors
{
public partial class GeolocationRequest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Microsoft.Maui.Devices.Sensors.GeolocationListeningFailedEventArgs.GeolocationLi
Microsoft.Maui.Devices.Sensors.IGeolocation.ListeningFailed -> System.EventHandler<Microsoft.Maui.Devices.Sensors.GeolocationListeningFailedEventArgs!>?
Microsoft.Maui.Storage.ISecureStorage.GetAsync(string! key) -> System.Threading.Tasks.Task<string?>!
static Microsoft.Maui.Devices.Sensors.Geolocation.ListeningFailed -> System.EventHandler<Microsoft.Maui.Devices.Sensors.GeolocationListeningFailedEventArgs!>!
override Microsoft.Maui.Devices.Sensors.GeolocationRequest.ToString() -> string!
static Microsoft.Maui.Storage.SecureStorage.GetAsync(string! key) -> System.Threading.Tasks.Task<string?>!
Microsoft.Maui.Devices.Sensors.IGeolocation.IsListeningForeground.get -> bool
Microsoft.Maui.Devices.Sensors.IGeolocation.LocationChanged -> System.EventHandler<Microsoft.Maui.Devices.Sensors.GeolocationLocationChangedEventArgs!>?
Expand Down
Loading

0 comments on commit 6a4c073

Please sign in to comment.