1

I need to get continuous location updates from the application for my application. To do so, I followed the Android Developer Guide to get location updates. Because location updates can't be received in background in API 26, I added a foreground service (Background location limits). However, I still only receive updates when an other activity which requests location updates is in the foreground.

Location Service:

public class LocationUpdateService extends Service {

private static final String TAG = LocationUpdateService.class.getSimpleName();

private static final String NOTIFICATION_CHANNEL_ID = "TrackNotification";
private static final int FOREGROUND_SERVICE_ID = 1;
private static final int NOTIFICATION_ID = 1;
public static final String STATUS_INTENT = "status";
private static final int CONFIG_CACHE_EXPIRY = 600;

private NotificationManager mNotificationManager;
private NotificationCompat.Builder mNotificationBuilder;

private DatabaseReference mDatabaseReference;

private FusedLocationProviderClient mFusedLocationProviderClient;
private LocationRequest mLocationRequest;

private FirebaseRemoteConfig mFirebaseRemoteConfig;

private String uid;

@Override
public void onCreate() {
    super.onCreate();

    uid = FirebaseAuth.getInstance().getUid();
    if(uid == null)
        stopSelf();

    mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
    FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder().build();
    mFirebaseRemoteConfig.setConfigSettings(configSettings);
    mFirebaseRemoteConfig.setDefaults(R.xml.remode_config_defaults);
    fetchRemoteConfig();

    mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
    mDatabaseReference = FirebaseDatabase.getInstance().getReference();

    mLocationRequest = new LocationRequest()
            .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
            .setFastestInterval(mFirebaseRemoteConfig.getLong("LOCATION_REQUEST_INTERVAL"))
            .setFastestInterval(mFirebaseRemoteConfig.getLong("LOCATION_REQUEST_INTERVAL_FASTEST"));

    bindNotification();
    setStatusMessage(R.string.connecting);


    startLocationTracking();
}

private LocationCallback mLocationCallback = new LocationCallback() {
    @Override
    public void onLocationResult(LocationResult locationResult) {
        Log.d(TAG,"Got location update!");
        if(locationResult == null)
            return;
        for(Location location : locationResult.getLocations()) {

            CustomLocation customLocation = LocationAdapter.toDatabaseLocation(location);
            mDatabaseReference.child("locations").child(uid).setValue(customLocation);
        }

    }

    @Override
    public void onLocationAvailability(LocationAvailability locationAvailability) {
        locationAvailability.isLocationAvailable();
        // TODO handle no location here
        super.onLocationAvailability(locationAvailability);
    }
};

@SuppressWarnings({"MissingPermission"})
private void startLocationTracking() {
    mFusedLocationProviderClient.requestLocationUpdates(mLocationRequest,mLocationCallback, Looper.myLooper());
}


private void fetchRemoteConfig() {
    mFirebaseRemoteConfig.fetch(CONFIG_CACHE_EXPIRY)
            .addOnCompleteListener(new OnCompleteListener<Void>() {
        @Override
        public void onComplete(@NonNull Task<Void> task) {
            Log.i(TAG,"Remote config fetched");
            mFirebaseRemoteConfig.activateFetched();
        }
    });
}

@Override
public void onDestroy() {
    setStatusMessage(R.string.not_tracking);
    mNotificationManager.cancel(NOTIFICATION_ID);
    mFusedLocationProviderClient.removeLocationUpdates(mLocationCallback);
    super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) { return null; }

private void bindNotification() {
    mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    PendingIntent resultPendingIntent = PendingIntent.getActivity(this,0,
            new Intent(this, MainActivity.class),PendingIntent.FLAG_UPDATE_CURRENT);

    mNotificationBuilder = new NotificationCompat.Builder(this,NOTIFICATION_CHANNEL_ID)
            .setCategory(NotificationCompat.CATEGORY_STATUS)
            .setShowWhen(false)
            .setSmallIcon(R.drawable.ic_car)
    //        .setColor(getColor(R.color.colorPrimary))
            .setContentTitle(getString(R.string.app_name))
            .setOngoing(true)
            .setContentIntent(resultPendingIntent);
    startForeground(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
}

/**
 *
 * @param message Status message to display
 */
private void setStatusMessage(String message) {
    mNotificationBuilder.setContentText(message);
    mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());

    Intent intent = new Intent(STATUS_INTENT);
    intent.putExtra(getString(R.string.status),message);
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

private void setStatusMessage(int resID) {
    setStatusMessage(getString(resID));
}
}

And I gets started with

startService(new Intent(this,LocationUpdateService.class));

Android Manifest:

<service android:name=".LocationUpdateService" />

EDIT1: I now tested it on older API versions (22) and the problem is still the same: As long as some app with location requests is in the foreground it works, otherwise not. Maybe it's a problem with the FusedLocationProviderClient but I don't know what. I only found code examples with the old FusedLocationProvider API which is now deprecated.

Marvin-wtt
  • 449
  • 6
  • 16
  • you could just try the answer of this question: [Get location as it changes](https://stackoverflow.com/questions/51223715/get-location-as-it-changes/51224073#51224073) – Farrokh Shahriari Jul 09 '18 at 20:13

1 Answers1

4

Have you tried debugging to make sure your service is being hit?

This might sound silly, but have you checked if your service is registered in your manifest? I know I've definitely run into that issue.

<service android:name=".LocationService"
        android:label="Location Service"
        android:exported="true"
        android:enabled="true"
        android:process=":location_background_service"/>

For getting the location, when I set mine up, I created a class that implemented android.location.LocationListener.

private class LocationListener implements android.location.LocationListener {
    Location mLastLocation;

    public LocationListener(String provider) {
        Log.e(TAG, "LocationListener " + provider);
        mLastLocation = new Location(provider);
    }

    @Override
    public void onLocationChanged(Location location) {
        Log.e(TAG, "onLocationChanged: " + location);
        mLastLocation.set(location);
    }

    @Override
    public void onProviderDisabled(String provider) {
        Log.e(TAG, "onProviderDisabled: " + provider);
    }

    @Override
    public void onProviderEnabled(String provider) {
        Log.e(TAG, "onProviderEnabled: " + provider);
    }

    @Override
    public void onStatusChanged(String provider, int status, Bundle extras) {
        Log.e(TAG, "onStatusChanged: " + provider);
    }
}

You can create multiple instances for different providers. In my case, I ended up using 2.

LocationListener[] mLocationListeners = new LocationListener[]{
        new LocationListener(LocationManager.GPS_PROVIDER),
        new LocationListener(LocationManager.NETWORK_PROVIDER)
};

Then I initialized a LocationManager that can set a polling rate for each a LocationListener.

private void initializeLocationManager() {
    Log.e(TAG, "initializeLocationManager");
    if (mLocationManager == null) {
        mLocationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
    }
}

Then in your Service onCreate function, initialize your LocationManager, and use one of the listeners as your main source and the other as a fallback.

try {
        mLocationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER,
                5 * 60 * 1000, //5 Minutes
                1F /*METERS*/,
                mLocationListeners[0]
        );
    } catch (java.lang.SecurityException ex) {
        Log.e(TAG, "failed to request location update. Insufficient permissions. ", ex);
        try {
            mLocationManager.requestLocationUpdates(
                    LocationManager.NETWORK_PROVIDER,
                    5 * 60 * 1000, //5 Minutes
                    1F /*METERS*/,
                    mLocationListeners[1]
            );
        } catch (java.lang.SecurityException e) {
            Log.e(TAG, "failed to request location update. Insufficient permissions. ", e);
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Network provider does not exist.", e);
        }
    } catch (IllegalArgumentException ex) {
        Log.e(TAG, "Network provider does not exist.", ex);
    }
}

(Sorry if this code is gross, it's a quick and dirty example.)

Kevin Ting
  • 56
  • 5
  • Yes. The notification is showing up and if I run an app like Google maps in foreground, I get location updates. – Marvin-wtt Jul 09 '18 at 23:53
  • And whats about the process attribute? – Marvin-wtt Jul 10 '18 at 09:16
  • From my understanding, since the service is separate from the actual application, it's an arbitrary name you give your foreground service so you can debug it easier. – Kevin Ting Jul 10 '18 at 18:46
  • I get all debug information on the console. If I run maps in foreground, I get the message "Got location update". But only then. It seems like the problem is with the Android background limitations but I'm not sure. – Marvin-wtt Jul 10 '18 at 19:56
  • Have you tried debugging it using this? https://stackoverflow.com/a/32080569/7655390 The console won't show messages from your foreground service unless you change the process you're listening for since they are technically separate processes. – Kevin Ting Jul 10 '18 at 20:10
  • Hmm. When I add the android:process attribute, the service crashes. However the log from the service gets printed in the Logcat without it. (Log.d(TAG,"Got location update!"); gets printed in the intervall when maps is in foreground) – Marvin-wtt Jul 11 '18 at 07:38
  • Hmmmm... are you accessing non-static methods from your app? by adding the android process name, it makes it independent of the original application process, allowing it to run on its own even when the app is closed or crashed, so if you are trying to access a method or variable that's only known within the context of the application, it could cause that crash? Sorry it's hard to figure out what's going on without seeing the error. – Kevin Ting Jul 11 '18 at 15:57
  • I made an edit to give an example of what I did for getting the location from the service – Kevin Ting Jul 11 '18 at 16:15