From 80cccea8863ca29c4e2c23c368bf545c4d4b33ff Mon Sep 17 00:00:00 2001 From: Alex Reich Date: Fri, 27 Dec 2019 06:38:33 -0800 Subject: [PATCH 1/7] Geolocation - Location Updates #290 - added ContinuousLocationListener ported from Geolocator.Plugin.GeolocationContinuousListener (cherry picked from commit 7c043df703a85f518527320aa434f43d4643ce1e) --- .../Geolocation/Geolocation.android.cs | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/Xamarin.Essentials/Geolocation/Geolocation.android.cs b/Xamarin.Essentials/Geolocation/Geolocation.android.cs index 8f0c33c99..da1e055c5 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.android.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.android.cs @@ -271,4 +271,91 @@ void ILocationListener.OnStatusChanged(string provider, [GeneratedEnum] Availabi } } } + + class ContinuousLocationListener : Java.Lang.Object, ILocationListener + { + readonly HashSet activeProviders = new HashSet(); + readonly LocationManager manager; + IList providers; + + string activeProvider; + AndroidLocation lastLocation; + TimeSpan timePeriod; + + public ContinuousLocationListener(LocationManager manager, TimeSpan timePeriod, IList providers) + { + this.manager = manager; + this.timePeriod = timePeriod; + this.providers = providers; + + foreach (var p in providers) + { + if (manager.IsProviderEnabled(p)) + activeProviders.Add(p); + } + } + + internal Action LocationHandler { get; set; } + + void ILocationListener.OnLocationChanged(AndroidLocation location) + { + if (location.Provider != activeProvider) + { + if (activeProvider != null && manager.IsProviderEnabled(activeProvider)) + { + var pr = manager.GetProvider(location.Provider); + var lapsed = GetTimeSpan(location.Time) - GetTimeSpan(lastLocation.Time); + + if (pr.Accuracy > manager.GetProvider(activeProvider).Accuracy + && lapsed < timePeriod.Add(timePeriod)) + { + location.Dispose(); + return; + } + } + + activeProvider = location.Provider; + } + + var previous = Interlocked.Exchange(ref lastLocation, location); + if (previous != null) + previous.Dispose(); + + LocationHandler?.Invoke(location); + } + + public void OnProviderDisabled(string provider) + { + lock (activeProviders) + activeProviders.Remove(provider); + } + + public void OnProviderEnabled(string provider) + { + if (provider == LocationManager.PassiveProvider) + return; + + lock (activeProviders) + activeProviders.Add(provider); + } + + public void OnStatusChanged(string provider, Availability status, Bundle extras) + { + switch (status) + { + case Availability.Available: + OnProviderEnabled(provider); + break; + + case Availability.OutOfService: + OnProviderDisabled(provider); + break; + } + } + + TimeSpan GetTimeSpan(long time) + { + return new TimeSpan(TimeSpan.TicksPerMillisecond * time); + } + } } From 4c5263b53d0dda054a533d88f8b0e7f6410715d1 Mon Sep 17 00:00:00 2001 From: Michael Fink Date: Wed, 9 Dec 2020 20:56:18 +0100 Subject: [PATCH 2/7] added public API, Android implementation and stubs for the other platforms --- .../Geolocation/Geolocation.android.cs | 88 +++++++++++++++++++ .../Geolocation/Geolocation.ios.macos.cs | 8 ++ .../Geolocation.netstandard.tvos.watchos.cs | 10 ++- .../Geolocation/Geolocation.shared.cs | 30 ++++++- .../Geolocation/Geolocation.tizen.cs | 8 ++ .../Geolocation/Geolocation.uwp.cs | 8 ++ 6 files changed, 150 insertions(+), 2 deletions(-) diff --git a/Xamarin.Essentials/Geolocation/Geolocation.android.cs b/Xamarin.Essentials/Geolocation/Geolocation.android.cs index da1e055c5..b7158da2e 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.android.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.android.cs @@ -17,6 +17,9 @@ public static partial class Geolocation const long twoMinutes = 120000; static readonly string[] ignoredProviders = new string[] { LocationManager.PassiveProvider, "local_database" }; + static ContinuousLocationListener continuousListener; + static List listeningProviders; + static async Task PlatformLastKnownLocationAsync() { await Permissions.EnsureGrantedAsync(); @@ -195,6 +198,91 @@ internal static bool IsBetterLocation(AndroidLocation location, AndroidLocation return false; } + + static bool PlatformIsListening() => continuousListener != null; + + static async Task PlatformStartListeningForegroundAsync(GeolocationRequest request) + { + if (IsListening) + throw new InvalidOperationException("This Geolocation is already listening"); + + await Permissions.EnsureGrantedAsync(); + + var locationManager = Platform.LocationManager; + + var enabledProviders = locationManager.GetProviders(true); + var hasProviders = enabledProviders.Any(p => !ignoredProviders.Contains(p)); + + if (!hasProviders) + throw new FeatureNotEnabledException("Location services are not enabled on device."); + + // get the best possible provider for the requested accuracy + var providerInfo = GetBestProvider(locationManager, request.DesiredAccuracy); + + // if no providers exist, we can't listen for locations + if (string.IsNullOrEmpty(providerInfo.Provider)) + return false; + + var allProviders = locationManager.GetProviders(false); + + listeningProviders = new List(); + if (allProviders.Contains(LocationManager.GpsProvider)) + listeningProviders.Add(LocationManager.GpsProvider); + if (allProviders.Contains(LocationManager.NetworkProvider)) + listeningProviders.Add(LocationManager.NetworkProvider); + + if (listeningProviders.Count == 0) + listeningProviders.Add(providerInfo.Provider); + + continuousListener = new ContinuousLocationListener(locationManager, request.Timeout, listeningProviders); + continuousListener.LocationHandler = HandleLocation; + + // start getting location updates + // make sure to use a thread with a looper + var looper = Looper.MyLooper() ?? Looper.MainLooper; + + var minTimeMilliseconds = (long)request.Timeout.TotalMilliseconds; + + foreach (var provider in listeningProviders) + locationManager.RequestLocationUpdates(provider, minTimeMilliseconds, providerInfo.Accuracy, continuousListener, looper); + + return true; + + void HandleLocation(AndroidLocation androidLocation) + { + OnLocationChanged(androidLocation.ToLocation()); + } + } + + static Task PlatformStopListeningForegroundAsync() + { + if (continuousListener == null) + return Task.FromResult(true); + + if (listeningProviders == null) + return Task.FromResult(true); + + var providers = listeningProviders; + continuousListener.LocationHandler = null; + + var locationManager = Platform.LocationManager; + + for (var i = 0; i < providers.Count; i++) + { + try + { + locationManager.RemoveUpdates(continuousListener); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine("Unable to remove updates: " + ex); + } + } + + continuousListener = null; + + return Task.FromResult(true); + } } class SingleLocationListener : Java.Lang.Object, ILocationListener diff --git a/Xamarin.Essentials/Geolocation/Geolocation.ios.macos.cs b/Xamarin.Essentials/Geolocation/Geolocation.ios.macos.cs index d9bb0d311..8180b13c5 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.ios.macos.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.ios.macos.cs @@ -67,6 +67,14 @@ void Cancel() tcs.TrySetResult(null); } } + + static bool PlatformIsListening() => false; + + static Task PlatformStartListeningForegroundAsync(GeolocationRequest request) => + throw ExceptionUtils.NotSupportedOrImplementedException; + + static Task PlatformStopListeningForegroundAsync() => + throw ExceptionUtils.NotSupportedOrImplementedException; } class SingleLocationListener : CLLocationManagerDelegate diff --git a/Xamarin.Essentials/Geolocation/Geolocation.netstandard.tvos.watchos.cs b/Xamarin.Essentials/Geolocation/Geolocation.netstandard.tvos.watchos.cs index 84953463f..f38d93b4d 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.netstandard.tvos.watchos.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.netstandard.tvos.watchos.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; using System.Threading; using System.Threading.Tasks; @@ -11,5 +11,13 @@ static Task PlatformLastKnownLocationAsync() => static Task PlatformLocationAsync(GeolocationRequest request, CancellationToken cancellationToken) => throw ExceptionUtils.NotSupportedOrImplementedException; + + static bool PlatformIsListening() => false; + + static Task PlatformStartListeningForegroundAsync(GeolocationRequest request) => + throw ExceptionUtils.NotSupportedOrImplementedException; + + static Task PlatformStopListeningForegroundAsync() => + throw ExceptionUtils.NotSupportedOrImplementedException; } } diff --git a/Xamarin.Essentials/Geolocation/Geolocation.shared.cs b/Xamarin.Essentials/Geolocation/Geolocation.shared.cs index f8f5b84fe..e4c324679 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.shared.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.shared.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -18,5 +17,34 @@ public static Task GetLocationAsync(GeolocationRequest request) => public static Task GetLocationAsync(GeolocationRequest request, CancellationToken cancelToken) => PlatformLocationAsync(request ?? new GeolocationRequest(), cancelToken); + + public static bool IsListening => PlatformIsListening(); + + public static Task StartListeningForegroundAsync(GeolocationRequest request) => + PlatformStartListeningForegroundAsync(request); + + public static Task StopListeningForegroundAsync() => + PlatformStopListeningForegroundAsync(); + + public static event EventHandler LocationChanged; + + internal static void OnLocationChanged(Location location) => + OnLocationChanged(new LocationEventArgs(location)); + + internal static void OnLocationChanged(LocationEventArgs e) => + LocationChanged?.Invoke(null, e); + } + + public class LocationEventArgs : EventArgs + { + public Location Location { get; } + + public LocationEventArgs(Location location) + { + if (location == null) + throw new ArgumentNullException(nameof(location)); + + Location = location; + } } } diff --git a/Xamarin.Essentials/Geolocation/Geolocation.tizen.cs b/Xamarin.Essentials/Geolocation/Geolocation.tizen.cs index 504b76584..903140270 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.tizen.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.tizen.cs @@ -63,5 +63,13 @@ static async Task PlatformLocationAsync(GeolocationRequest request, Ca return lastKnownLocation; } + + static bool PlatformIsListening() => false; + + static Task PlatformStartListeningForegroundAsync(GeolocationRequest request) => + throw ExceptionUtils.NotSupportedOrImplementedException; + + static Task PlatformStopListeningForegroundAsync() => + throw ExceptionUtils.NotSupportedOrImplementedException; } } diff --git a/Xamarin.Essentials/Geolocation/Geolocation.uwp.cs b/Xamarin.Essentials/Geolocation/Geolocation.uwp.cs index 6b8897c0f..eeb7effae 100644 --- a/Xamarin.Essentials/Geolocation/Geolocation.uwp.cs +++ b/Xamarin.Essentials/Geolocation/Geolocation.uwp.cs @@ -50,5 +50,13 @@ void CheckStatus(PositionStatus status) } } } + + static bool PlatformIsListening() => false; + + static Task PlatformStartListeningForegroundAsync(GeolocationRequest request) => + throw ExceptionUtils.NotSupportedOrImplementedException; + + static Task PlatformStopListeningForegroundAsync() => + throw ExceptionUtils.NotSupportedOrImplementedException; } } From f5b1c21de32953b588cc745bedeb8f14213881a2 Mon Sep 17 00:00:00 2001 From: Michael Fink Date: Wed, 9 Dec 2020 20:57:21 +0100 Subject: [PATCH 3/7] added Samples view model and view for new Geolocation foreground listener implementation --- Samples/Samples/View/GeolocationPage.xaml | 10 +++ .../Samples/ViewModel/GeolocationViewModel.cs | 78 ++++++++++++++++++- 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/Samples/Samples/View/GeolocationPage.xaml b/Samples/Samples/View/GeolocationPage.xaml index b0cb7339e..2d1382e03 100644 --- a/Samples/Samples/View/GeolocationPage.xaml +++ b/Samples/Samples/View/GeolocationPage.xaml @@ -27,6 +27,16 @@ IsEnabled="{Binding IsNotBusy}" HorizontalOptions="FillAndExpand" />