With the release of Oreo/Android 8.0, I'm wanting to replace my GPS foreground service with a scheduled job.
I've looked at the Xamarin Example for Job Scheduler, and honestly I find it quite confusing.
Here is all I want to do:
- User taps a button
- A job is created and scheduled.
- The job is called instantly, and every 30 seconds afterwards until the user taps the button again
The job will be the replacement for my GPS service (which implementes ILocationListener).
Could anyone help clear up what I need to do to achieve this?
I have tried .SetPeriodic(30000), which doesn't seem to work on my test device running Oreo.
The example uses .SetMinimumLatency() and .SetOverrideDeadline() to set some sort of schedule range. Can I set both of these to 30000 (30 seconds) to achieve what I need? There also seems to be a 15 minute minimum time for scheduled jobs.
The example uses handlers and passes parameters, etc. Is this required for a Job Scheduler? There's a lot going on for a basic example project, and I'm just getting confused about the requirements.
Thanks in advance, hoping someone can help me clear this up a bit.
Cheers.
EDIT - EXAMPLE CODE
private void StartTracking()
{
Log.Debug(TAG, "Starting Tracking");
var component = new ComponentName(Context, Java.Lang.Class.FromType(typeof(LocationJobService)));
//// This will run a service as normal on pre-Oreo targets, and use the Job Scheduler on Oreo+.
//// Unfortunately we can't use it as there is no way to cancel it.
//var locationJobIntent = new Intent(Context, typeof(LocationJobIntentService));
//JobIntentService.EnqueueWork(Context, component, LocationJobIntentService.JobId, locationJobIntent);
var deadline = 15 * 1000;
var retryTime = 60 * 1000;
var builder = new JobInfo.Builder(LocationJobService.JobId, component)
.SetMinimumLatency(0)
.SetOverrideDeadline(deadline)
.SetBackoffCriteria(retryTime, BackoffPolicy.Linear)
.SetRequiredNetworkType(NetworkType.Any);
Log.Debug(TAG, "Scheduling LocationJobService...");
var result = _jobScheduler.Schedule(builder.Build());
if (result != JobScheduler.ResultSuccess)
{
Log.Warn(TAG, "Job Scheduler failed to schedule job!");
}
}
And likewise, this is hit when a the user taps the button again to stop the location updates
private void StopTracking()
{
Log.Debug(TAG, "Stopping Tracking");
_jobScheduler.CancelAll();
}
And here's my Job Service
[Service(Name = "my.assembly.name.etc.LocationJobIntentService", Permission = "android.permission.BIND_JOB_SERVICE")]
public class LocationJobService : JobService, ILocationListener
{
public const int JobId = 69;
private const long LOCATION_UPDATE_INTERVAL = 5 * 1000;
private const string NOTIFICATION_PRIMARY_CHANNEL = "default";
private const int NOTIFICATION_SERVICE_ID = 261088;
private static readonly string TAG = typeof(LocationJobService).FullName;
private LocationManager _locationManager;
private string _locationProvider;
#region Base Overrides
public override void OnCreate()
{
base.OnCreate();
Log.Debug(TAG, "OnCreate called.");
_locationManager = (LocationManager)GetSystemService(LocationService);
//// TODO: Start the Foreground Service, and display the required notification.
//var intent = new Intent(this, typeof(LocationJobService));
//if (AndroidTargetHelper.IsOreoOrLater())
//{
// StartForegroundService(intent);
//}
//else
//{
// StartService(intent);
//}
//CreateForegroundNotification("It started! yay!");
}
/// <summary>Called to indicate that the job has begun executing.</summary>
/// <remarks>This method executes on the main method. Ensure there is no blocking.</remarks>
public override bool OnStartJob(JobParameters jobParameters)
{
Log.Debug(TAG, "OnStartJob called.");
Task.Run(() =>
{
Log.Debug(TAG, "Starting location updates...");
StartLocationUpdates();
Log.Debug(TAG, "Location updates started. Stopping job.");
JobFinished(jobParameters, true);
});
// TODO: We need a way to cancel the SERVICE (eg. Foreground notifications, etc) if required. This needs to happen on both logging out, and when the user disables tracking.
// Perhaps the notifications need to happen on the Fragment, as opposed to here in the job service?
// Will this job be doing background work?
return true;
}
/// <summary>This method is called if the system has determined that you must stop execution of your job even before you've had a chance to call JobFinished().</summary>
public override bool OnStopJob(JobParameters @params)
{
// The OS has determined that the job must stop, before JobFinished() has been called.
// TODO: Here, we want to restart the Foreground service to continue it's lifespan, but ONLY if the user didn't stop tracking.
Log.Debug(TAG, "OnStopJob called.");
// Reschedule the job?
return false;
}
public override void OnDestroy()
{
base.OnDestroy();
// TODO: StopForeground(true);
Log.Debug(TAG, "OnDestroy called.");
}
#endregion
#region ILocationListener Members
public void OnLocationChanged(Location location)
{
Log.Debug(TAG, String.Format("Location changed to '{0}, {1}'", location.Latitude, location.Longitude));
StopLocationUpdates();
// TODO: Do we finish the job here? Or right after we ASK for the location updates? (job params would have to be accessible)
//JobFinished(_jobParameters, true);
// THIS IS WHERE THE LOCATION IS POSTED TO THE API
await PostLocationPing(location.Latitude, location.Longitude);
}
public void OnProviderDisabled(string provider)
{
Log.Debug(TAG, String.Format("Provider '{0}' disabled by user.", provider));
// TODO: Get new provider via StartLocationupdates()?
}
public void OnProviderEnabled(string provider)
{
Log.Debug(TAG, String.Format("Provider '{0}' enabled by user.", provider));
// TODO: Get new provider via StartLocationupdates()?
}
public void OnStatusChanged(string provider, [GeneratedEnum] Availability status, Bundle extras)
{
Log.Debug(TAG, String.Format("Provider '{0}' availability has changed to '{1}'.", provider, status.ToString()));
// TODO: Get new provider via StartLocationupdates()?
}
#endregion
private async Task<string> PostLocationPing(double latitude, double longitude, bool isFinal = false)
{
var client = new NetworkClient();
var locationPing = new LocationPingDTO()
{
TimestampUtc = DateTime.UtcNow,
Latitude = latitude,
Longitude = longitude,
};
return await client.PostLocationAndGetSiteName(locationPing);
}
#region Helper Methods
private void StartLocationUpdates()
{
// TODO: Specify the criteria - in our case we want accurate enough, without burning through the battery
var criteria = new Criteria
{
Accuracy = Accuracy.Fine,
SpeedRequired = false,
AltitudeRequired = false,
BearingRequired = false,
CostAllowed = false,
HorizontalAccuracy = Accuracy.High
};
// Get provider (GPS, Network, etc)
_locationProvider = _locationManager.GetBestProvider(criteria, true);
// Start asking for locations, at a specified time interval (eg. 5 seconds to get an accurate reading)
// We don't use RequestSingleUpdate because the first location accuracy is pretty shitty.
_locationManager.RequestLocationUpdates(_locationProvider, LOCATION_UPDATE_INTERVAL, 0.0f, this);
}
private void StopLocationUpdates()
{
Log.Debug(TAG, "StopLocationUpdates called.");
_locationManager.RemoveUpdates(this);
}
#endregion
}
}