question

BrianErickson-9266 avatar image
0 Votes"
BrianErickson-9266 asked RobCaplan edited

GetLocationAsync inside a foreground service

I've got an Android foreground service that uses voice recognition and geolocation. Xamarin doesn't help with voice recognition but I'm using Essentials built-in support for geolocation. I've put these in a foreground service because they need to run for several hours while in the background.

The service is started while the app is in the foreground and the voice recognition part of the service works fine. However, the geolocation stops working at random times. Sometimes it stays working for 60 minutes, and sometimes for just 5 minutes. The call to Geolocation.GetLocationAsync just hangs. I finally implemented a timer so I can tell the user to turn on the phone. This gets geolocation working again...for a while.

I thought I understood from this, https://developer.android.com/about/versions/oreo/background-location-limits, that geolocation would be available as long as the service was running.

Relevant code:

       private CancellationTokenSource cts;
       private bool CancelFlag = false;
    
       //Initialize location
       public void LocationServicesInit()
       {
          CancelFlag = false;
          cts = null;
          LocationTimer = null;
       }
    
       private void StartLocationTimer( double NewInterval )
       {
          if (LocationTimer == null)
          {
             LocationTimer = new System.Timers.Timer();
             LocationTimer.Elapsed += LocationTimer_Elapsed;
          }
    
          LocationTimer.Interval = NewInterval;
          LocationTimer.Enabled = true;
       }
       private void StopLocationTimer()
       {
          if (LocationTimer != null)
             LocationTimer.Enabled = false;
       }
       private void LocationTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
       {
          GlobalVariables.LogMessage.RaiseLogMessageEvent("Location loop has unexpectedly been terminated or is stuck and needs to be restarted.");
          _ = Xamarin.Essentials.TextToSpeech.SpeakAsync("Location loop has unexpectedly been terminated or is stuck and needs to be restarted.");
       }
    
       public void StopLocationLoop()
       {
          GlobalVariables.LogMessage.RaiseLogMessageEvent("Location measurment stopped.");
          StopLocationTimer();
    
          CancelFlag = true;
          if (cts != null)
             cts.Cancel();
       }
    
       const double YardsPerMile = 1760.0;
    
       /// <summary>
       /// Continuously request location info until cancelled.
       /// </summary>
       /// <param name="CurrentPositionData"></param>
       /// <returns></returns>
       public async Task StartLocationLoop(ESPosition KeepCurrentPos)
       {
          const double LocationTime = 10.0;
          const double LocationTimerInterval = LocationTime * 1500;
    
          int LocationStuckCount;
          ESPosition CurrentPos;
    
    
          GlobalVariables.LogMessage.RaiseLogMessageEvent("Location measurment started.");
          GeolocationRequest request = new GeolocationRequest(GeolocationAccuracy.Best, TimeSpan.FromSeconds(LocationTimerInterval));
    
          CancelFlag = false;
    
          LocationStuckCount = 0;
          StartLocationTimer(LocationTimerInterval);
    
          while (!CancelFlag)
          {
             CurrentPos = await GetCurrentLocation(request);
             if (CurrentPos != null)
             {
                LocationStuckCount = 0;
                KeepCurrentPos.CopyHere(CurrentPos);
                KeepCurrentPos.IncrementCount();
             }
             else
                ++LocationStuckCount;
    
             if( LocationStuckCount > 10 )
             {
                GlobalVariables.LogMessage.RaiseLogMessageEvent("Location has stopped working and needs to be restarted.");
                _ = Xamarin.Essentials.TextToSpeech.SpeakAsync("Location has stopped working and needs to be restarted.");
                StopLocationLoop();
             }
    
             StopLocationTimer();
             StartLocationTimer(LocationTimerInterval);
    
             await Task.Delay(1000);
          }
    
          StopLocationTimer();
       }
    
       //=======================================================================
       /// <summary>
       /// Returns current location information using Xamarin.  This method
       /// will normally be awaited as the returned data is the only way
       /// to see the geolocation data.
       /// </summary>
       /// <returns>Xamarin location object</returns>
       //=======================================================================
       public async Task<ESPosition> GetCurrentLocation( GeolocationRequest req )
       {
          Xamarin.Essentials.Location xlocation;
          ESPosition Location = null;
          try
          {
    
             if (ESPermissions.LocationGranted)
             {
                cts = new CancellationTokenSource();
                xlocation = await Geolocation.GetLocationAsync(req, cts.Token);
                if (xlocation != null)
                   Location = new ESPosition(xlocation);
                cts = null;
             }
          }
          catch (FeatureNotSupportedException fnsEx)
          {
             GlobalVariables.LogMessage.RaiseLogMessageEvent(fnsEx.Message);
          }
          catch (FeatureNotEnabledException fneEx)
          {
             GlobalVariables.LogMessage.RaiseLogMessageEvent(fneEx.Message);
          }
          catch (PermissionException pEx)
          {
             GlobalVariables.LogMessage.RaiseLogMessageEvent(pEx.Message);
          }
          catch (Exception ex)
          {
             GlobalVariables.LogMessage.RaiseLogMessageEvent(ex.Message);
          }
    
          return (Location);
       }

Any help understanding what's happening would be greatly appreciated.
Brian



dotnet-xamarin
· 5
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Recap:

I've got a foreground service the uses voice recognition and geolocation and my app needs to be in the background for 4 hours or so (time enough to play 18 holes of golf).

No issues with voice recognition.

After some amount of time (1-2 hours) Geolocation starts returning the same values over and over and the accuracy gets very bad. Bringing the app to the foreground allows Geolocation to work again. However, when the app is put into the background again, Geolocation stops working in just a few minutes.

So, I've spent a fair amount of time refactoring my app. So, when my app is brought to the foreground, the service is stopped and restarted. Hopefully that will help. I'll post back here tomorrow after my round of golf...

Still looking for a real solution but I'm becoming convinced that either the documentation is incorrect or there is a bug in Android's Geolocation implementation. I'll keep reading...

0 Votes 0 ·

Played 9 holes today...

Stopping the service and re-starting it does nothing. Geolocation works when the app is in the foreground but starts failing just a few minutes after going to the background.

I can't tell the difference between Geolocation failing and me standing still waiting for my turn to hit...

I'm considering using Android's native Geolocation tools in place of Xamarin, just in case the problem lies with Xamarin.

0 Votes 0 ·

You can use xamarin.android api to get the location data by a task, And here is source code of Geolocation, I find it use LocationManager as well, which device did you test? Please test your code in google device, because other devices install custom Android OS, it will have limiation for android foreground service or location service.

https://github.com/xamarin/Essentials/blob/main/Xamarin.Essentials/Geolocation/Geolocation.android.cs

0 Votes 0 ·
Show more comments

1 Answer

BrianErickson-9266 avatar image
0 Votes"
BrianErickson-9266 answered LeonLu-MSFT commented

So, it looks like wake locks might be the answer. I added:

          WLock = (GetSystemService(Context.PowerService) as PowerManager).NewWakeLock(WakeLockFlags.Partial, "MyWakeLock");
          if (WLock != null)
             WLock.Acquire();

The problem with GeoLocation might be fixed. I'm playing 18 holes on Tues. If the GeoLocation lasts the entire round I'll mark this answered.

rant I'm really beginning to hate Android.

· 8
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

So GeoLocation doesn't hang anymore, that's good. However, instead of hanging it returns the same value (which in my case we +- 48 yards) over and over again. That's not so good.

0 Votes 0 ·

Where did you test this code in Android device or emulator? If put the application to foreground. then execute your GeoLocation , if you can get the correct result.

0 Votes 0 ·

It was tested on a Galaxy S9 and yes, once I turned my phone back on, Geolocationi started to work again.

For my app, requiring the app to be brought to the foreground is undesirable behavior. So, I will continue to look for a resolution.

Further, there is no indication from the OS that this is happening. So, I'll have to right code to detect it and see what I can do to mitigate the effects of having 'bad' positions reports for a few seconds.

rant It's official, I hate Android.

0 Votes 0 ·

So, I'm pretty sure I'm being affected by this:

If background location access is essential for your app, keep in mind that Android preserves device battery life by setting background location limits on devices that run Android 8.0 (API level 26) and higher. On these versions of Android, if your app is running in the background, it can receive location updates only a few times each hour.

Now, the documentation invites the reader to "Learn more about background location limits". When you follow that link you'll find:

Consider whether your app's use cases for running in the background cannot succeed at all if your app receives infrequent location updates. If this is the case, you can retrieve location updates more frequently by performing one of the following actions:
Bring your app to the foreground.
Start a foreground service in your app by calling startForegroundService(). When such a foreground service is active, it appears as an ongoing notification in the notification area.

My app cannot succeed if I can't get location in real time. Further the location requests are already in a Foreground Service. So, I'm not sure where to go from here but I'll keep reading.




0 Votes 0 ·

So, now I've found this:

Declare foreground service types
If your app targets Android 10 (API level 29) or higher and accesses location information in a foreground service, declare the location foreground service type as an attribute of your <service> component.
If your app targets Android 11 (API level 30) or higher and accesses the camera or microphone in a foreground service, declare the camera or microphone foreground service types, respectively, as attributes of your <service> component.

Hadn't noticed this before. Adding this and removing the wake lock....We'll see what happens...

0 Votes 0 ·

Ok, waiting for you update.

0 Votes 0 ·

Added the foreground service type and set it to Microphone and Location.

Still after a couple hours, location starts returning the same value over and over. This time accuracy was +- 22 yards.

The explicit wake lock does not appear to be necessary.

So, still reading ...

0 Votes 0 ·
Show more comments