For my specific use case, I need to fetch the device's real-time location updates and pass them to a remote server at 15 15-second frequency(or less than 15 seconds). To achieve this, I wrote a foreground service that initializes a FusedLocationProviderClient with interval and fastestIntetval as 15s. I am using an external HandlerThread's looper to invoke requestLocationUpdates.
When the app is in the foreground, location updates are received in onLocationResult callback of LocationCallback and also if I invoke getLastLocation() then it returns the same location. But whenever the app is minimized (service still running, the foreground notification is there), onLocationResult does not gets triggered and invoking getLastLocation() returns null.
Is this expected behavior from Fused? If not, how can I fix this issue. I am sharing the codes bellow:
private val mLocationRequest =
LocationRequest().apply {
interval = updateFrequency
fastestInterval = updateFrequency
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
}
private val mFusedLocationClient: FusedLocationProviderClient by lazy {
LocationServices.getFusedLocationProviderClient(
this
)
}
private val mLocationCallback: LocationCallback by lazy {
object : LocationCallback() {
override fun onLocationResult(p0: LocationResult?) {
super.onLocationResult(p0)
p0?.lastLocation?.let {
if (System.currentTimeMillis() - (Constants.lastLocationFetchTimestamp
?: 0) > minTimeBlockThreshold && Constants.currentLocation != null
) {
}
}
}
}
}
private lateinit var mServiceHandler: Handler
override fun onCreate() {
super.onCreate()
getLastLocation()
val handlerThread = HandlerThread(LocationUpdatesService::class.java.simpleName)
handlerThread.start()
mServiceHandler = Handler(handlerThread.looper)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_NOT_STICKY
}
override fun onBind(p0: Intent): IBinder {
super.onBind(p0)
stopForeground(true)
return mBinder
}
override fun onRebind(intent: Intent?) {
super.onRebind(intent)
stopForeground(true)
}
override fun onUnbind(intent: Intent?): Boolean {
if (!mChangingConfiguration && preferenceManager.requestingLocationUpdates()) {
startForeground(
(applicationContext as ContractorApp).notificationIdForForegroundService,
NotificationUtils.getForegroundServiceNotification(applicationContext)
)
}
return true
}
override fun onDestroy() {
super.onDestroy()
mServiceHandler.removeCallbacksAndMessages(null)
}
fun requestLocationUpdates() {
try {
mFusedLocationClient.removeLocationUpdates(mLocationCallback)
preferenceManager.setRequestingLocationUpdates(true)
startService(Intent(applicationContext, LocationUpdatesService::class.java))
mFusedLocationClient.requestLocationUpdates(
mLocationRequest,
mLocationCallback, mServiceHandler.looper
)
startRecursiveForceLocationFetchCall()
Constants.locationUpdatesRequested = true
} catch (unlikely: SecurityException) {
Utils.reportNonFatals(unlikely)
preferenceManager.setRequestingLocationUpdates(false)
}
}
fun removeLocationUpdates() {
try {
Constants.locationUpdatesRequested = false
mFusedLocationClient.removeLocationUpdates(mLocationCallback)
preferenceManager.setRequestingLocationUpdates(false)
preferenceManager.remove(Constants.ACTIVE_VEHICLE_TYPE)
stopRecursiveForceLocationFetchCall()
stopSelf()
} catch (unlikely: SecurityException) {
Utils.reportNonFatals(unlikely)
preferenceManager.setRequestingLocationUpdates(true)
}
}
fun updateNotificationText() {
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).notify(
(applicationContext as ContractorApp).notificationIdForForegroundService,
NotificationUtils.getForegroundServiceNotification(applicationContext)
)
}
private fun getLastLocation() {
try {
mFusedLocationClient.lastLocation
?.addOnCompleteListener { task ->
if (task.isSuccessful && task.result != null) {
if (task.result != null) {
Constants.currentLocation = task.result
}
}
}
} catch (unlikely: SecurityException) {
Utils.reportNonFatals(unlikely)
}
}
private fun onNewLocation(location: Location) {
// logic to send location to remote server
}
private var forcePing: Job? = null
private fun startRecursiveForceLocationFetchCall() {
if (forcePing == null) {
forcePing = GlobalScope.launch(Dispatchers.IO){
while (true) {
if (ContextCompat.checkSelfPermission(
this@LocationUpdatesService,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
mFusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
// Use the last known location
val latitude = location.latitude
val longitude = location.longitude
onNewLocation(location)
} else {
Timber.tag("FORCE_PING").d("Last known location is null")
}
}.addOnFailureListener {
Utils.reportNonFatals(it)
}.addOnCanceledListener {
Timber.tag("FORCE_PING").d("Issue")
}
} else {
// No permission
}
delay(TimeUnit.SECONDS.toMillis(15))
}
}
} else {
forcePing?.cancel()
forcePing = null
startRecursiveForceLocationFetchCall()
}
}
private fun stopRecursiveForceLocationFetchCall() {
if (forcePing != null) {
forcePing?.cancel()
forcePing = null
}
}