Reputation: 11
I am developing an app for taxi drivers, that report vehicle's position to dispatching webapp. I have managed to reliably get device location using FusedLocationProviderClient and position updates, that are being sent through Foreground service. Unfortunately, my tablet (Huawei MediaPad T5 - Android Oreo 8.0) is actively killing the service while the app is not in foreground. According to Android documentation, the way I'm doing this is correct (if not, please, feel free to correct me).
I am attaching service source code below:
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationCallback;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import driver.taxis.dcsoft.cz.R;
import driver.taxis.dcsoft.cz.communication.CommunicatorManager;
import driver.taxis.dcsoft.cz.gui.activities.MainActivity;
import driver.taxis.dcsoft.cz.preferences.PreferencesConstants;
import driver.taxis.dcsoft.cz.preferences.PreferencesManager;
public class UpdateLocationService extends Service {
private static final String TAG = "LocationService";
private FusedLocationProviderClient mFusedLocationClient;
private final static long UPDATE_INTERVAL = 15 * 1000; /* 4 secs */
private final static long FASTEST_INTERVAL = 7 * 1000; /* 2 sec */
private LocationCallback locationCallback;
@Override
public void onCreate() {
super.onCreate();
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
if (Build.VERSION.SDK_INT >= 26) {
String CHANNEL_ID = "my_channel_01";
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
"My Channel",
NotificationManager.IMPORTANCE_DEFAULT);
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setOngoing(true)
.setContentTitle("Aplikace " + MainActivity.getContext().getString(R.string.app_name) + " odesílá polohu na pozadí.")
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: called.");
getLocation();
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
if(locationCallback != null) {
mFusedLocationClient.removeLocationUpdates(locationCallback);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void getLocation() {
// Create the location request to start receiving updates
LocationRequest mLocationRequestHighAccuracy = new LocationRequest();
mLocationRequestHighAccuracy.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequestHighAccuracy.setInterval(UPDATE_INTERVAL);
mLocationRequestHighAccuracy.setFastestInterval(FASTEST_INTERVAL);
// new Google API SDK v11 uses getFusedLocationProviderClient(this)
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d(TAG, "getLocation: stopping the location service.");
stopSelf();
return;
}
Log.d(TAG, "getLocation: getting location information.");
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(LocationResult locationResult) {
Log.d(TAG, "onLocationResult: got location result.");
Location location = locationResult.getLastLocation();
if (location != null) {
String vehicleId = PreferencesManager.loadStringPreference(PreferencesConstants.SPZ_STRING, "");
if(vehicleId.isEmpty() || vehicleId == null) {
return;
}
CommunicatorManager.INSTANCE.sendPositionRequest(location);
}
}
};
mFusedLocationClient.requestLocationUpdates(mLocationRequestHighAccuracy, locationCallback,
Looper.myLooper()); // Looper.myLooper tells this to repeat forever until thread is destroyed
}
}
Upvotes: 1
Views: 960
Reputation: 305
I am coding a similar app to track my staff as they visit clients (completely with their permission, of course) so their app can notify the next client by SMS when they are on their way to their job. I found that with the app in focus in the foreground, GPS worked perfectly. However, if the user switched to another screen, such as making a call, or the screen locked or went to sleep, the GPS stopped returning any data at all.
I eventually discovered it was due to permissions—I wasn’t requesting the correct permissions or verifying that those permissions were granted before calling the GPS functions. This was causing the whole app to pause while waiting for the GPS to return its data. So, I added these to my AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
This is my function, called from onCreate in my main activity, to request the user's permission to allow background GPS access. Since background GPS is a separate permission from running GPS in the foreground, both permissions need to be explicitly requested. Background GPS access uses a lot of power and could potentially be used to track a person's phone unknowingly, so users must explicitly grant permission for your app to use it.
You can always use the fused location service, which provides the most recent data from the GPS system. However, if the user isn’t running a GPS app at the same time, this data can be up to 15 minutes old—making it unsuitable for monitoring real-time travel.
private fun requestLocationPermission() {
val fineLocationGranted = ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
val backgroundLocationGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
} else {
true
}
if (!fineLocationGranted) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
LOCATION_PERMISSION_REQUEST_CODE
)
} else {
Log.d("MainActivity", "ACCESS_FINE_LOCATION already granted.")
}
if (fineLocationGranted && !backgroundLocationGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_BACKGROUND_LOCATION),
BACKGROUND_LOCATION_PERMISSION_REQUEST_CODE
)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.d("MainActivity", "ACCESS_BACKGROUND_LOCATION already granted.")
}
}
Hope this helps
Upvotes: 0
Reputation: 353
Unfortunately the problem is not in your code.
In Android (6+) there is no service that runs indefinitely and in a safe way on all devices brands/type without any precaution of the user, because any device brand implemented in a different way the settings to limit, kill, or keep safe the background services/apps (yes, it's a big mess).
As first step, users should go to the Android settings and turn off all battery monitoring and optimizations for your app. On Android 9+ they should check also that the application is NOT background restricted and verify that the background activity is allowed.
On some brands they have to whitelist the background apps, whilst for some others you have to set the "high performances" power saving mode.
To give a concrete example, on Android 11 Samsung will prevent apps work in background by default unless the user excludes the app from battery optimizations: he should go on Android Settings -> Apps -> YOUR_APP -> Battery -> Battery optimization -> All apps -> YOUR_APP -> Don’t optimize.
Moreover, other battery optimizers (like for example AccuBattery by Digibites) could restrict the background use, and also some anti-viruses are very aggressive with long running apps, and must be correctly set.
Maybe in your case, where the update timing is not crucial, you could use a normal sticky service (START_STICKY
);
As alternative, you should check that the foreground service has all the condition to run safely in background (some of them are checkable, others not).
You can view a complete example of foreground service for GPS here. It is made to work in background reliably (it includes also a Wakelock to prevent phone from sleeping). It works well, but on some devices the users have to disable the optimizations described above.
The app takes care to check that the background activity is allowed:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ActivityManager activityManager = (ActivityManager)getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
if ((activityManager != null) && (activityManager.isBackgroundRestricted())) {
isBackgroundActivityRestricted = true;
} else {
isBackgroundActivityRestricted = false;
}
} else {
isBackgroundActivityRestricted = false;
}
and that the GPS is ON and accessible.
Upvotes: 0