Location Updates on Boot - turn on and off in activity - android

I started with Googles LocationUpdatesPendingIntent example.
I've moved the location stuff from the Main Activity to the onBoot broadcast receiver as I need location updates to start straight away when the device boots. This works perfectly and provides a notification in the status bar.
But how do I go about turning the location updates on and off from an Activity?
This is for polling vehicle locations.
This is my BroadcastReceiver:
public class StartupComplete1 extends BroadcastReceiver {
private static final long UPDATE_INTERVAL = 10000; // Every 10 seconds.
private static final long FASTEST_UPDATE_INTERVAL = 5000; // Every 5 seconds
private static final long MAX_WAIT_TIME = UPDATE_INTERVAL * 2; // Every 20 seconds.
private LocationRequest mLocationRequest;
private FusedLocationProviderClient mFusedLocationClient;
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(Intent.ACTION_BOOT_COMPLETED)) {
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(context);
createLocationRequest();
try {
mFusedLocationClient.requestLocationUpdates(mLocationRequest, getPendingIntent(context));
} catch (SecurityException e) {
Toast.makeText(context, "Error - Cant start location updates", Toast.LENGTH_LONG).show();
e.printStackTrace();
}
}
}
private PendingIntent getPendingIntent(Context context) {
Intent intent = new Intent(context, LocationUpdatesBroadcastReceiver.class);
intent.setAction(LocationUpdatesBroadcastReceiver.ACTION_PROCESS_UPDATES);
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
mLocationRequest.setMaxWaitTime(MAX_WAIT_TIME);
}

It's not a great idea to start checking for location updates in broadcast receiver. Based on android documentation for broadcast receiver
As a general rule, broadcast receivers are allowed to run for up to 10 seconds before they system will consider them non-responsive and ANR the app. Since these usually execute on the app's main thread, they are already bound by the ~5 second time limit of various operations that can happen there (not to mention just avoiding UI jank), so the receive limit is generally not of concern. However, once you use goAsync, though able to be off the main thread, the broadcast execution limit still applies, and that includes the time spent between calling this method and ultimately PendingResult.finish().
This could cause ANR when location updates takes longer time for responding, especially if you are indoors.
You should start a sticky service on Boot completed broadcast receiver's onReceive(). MainActivity can then bind to this service to perform the necessary action.
This approach could have issues if you are targeting Android O. Please check this post which explains background location gathering on Android O.

Related

locationListener doesn't work after 30 seconds on foreground service

I have created a service which finds and then stores the user's coordinates in an SQLite database.
public class GPS_Service extends Service {
DatabaseHelper myDb;
private LocationListener locationListener;
private LocationManager locationManager;
private String latitude, longitude;
#Override
public void onCreate() {
super.onCreate();
myDb = new DatabaseHelper(this);
}
#SuppressLint("MissingPermission")
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Service")
.setContentText("Coordinates Location Running")
.setContentIntent(pendingIntent)
.build();
startForeground(1, notification);
locationListener = new LocationListener() {
#Override
public void onLocationChanged(Location location) {
Log.d("myTag", "Hello");
latitude = String.valueOf(location.getLatitude());
longitude = String.valueOf(location.getLongitude());
insertCoordinates(latitude, longitude);
Intent i = new Intent("location_update");
i.putExtra("latitude", latitude);
i.putExtra("longitude",longitude);
sendBroadcast(i);
}
#Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
#Override
public void onProviderEnabled(String provider) {
}
#Override
public void onProviderDisabled(String provider) {
Intent i = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
};
locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 10000, 0, locationListener);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
if(locationManager != null)
locationManager.removeUpdates(locationListener);
}
private void insertCoordinates(String latitude, String longitude) {
boolean inserted = myDb.insertData(latitude, longitude); //Insert coordinates
//Check if insertion is completed
if(inserted)
Toast.makeText(GPS_Service.this, "Coordinates Inserted", Toast.LENGTH_SHORT).show();
else
Toast.makeText(GPS_Service.this, "Coordinates Not Inserted", Toast.LENGTH_SHORT).show();
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I can either start or stop the service from the MainActivity like this
private void enable_buttons() {
buttonStartService.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent serviceIntent = new Intent(getApplicationContext(), GPS_Service.class);
//Checks if the SDK version is higher than 26 to act accordingly
ContextCompat.startForegroundService(MainActivity.this, serviceIntent);
}
});
buttonStopService.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent serviceIntent = new Intent(MainActivity.this, GPS_Service.class);
stopService(serviceIntent);
}
});
}
The problem is that when I start this service, if I either completely close the app or leave it in the background, the locationListener will work for 30 seconds and then it will stop. If I reopen the app, the service continues to work from where it stopped. Also I checked in the developer options if the service is running, and it indeed is even though the locationListener doesn't output the expected results. Any ideas?
TL;DR:
Add android:foregroundServiceType="location" to your Service's manifest entry.
EXPLANATION
This new behavior, for Android 10, is exactly as you've described: Even though you may be using a foreground service, 30 seconds after your app leaves the screen -- location updates cease.
You might've noticed that Android 10 devices present two new choices to the user when granting location permissions (for legacy (API < 29) apps, or apps that declare the ACCESS_BACKGROUND_LOCATION permission):
"Allow all the time"
"Allow only while using this app"
"Allow only while using this app" effectively means, "Allow while the app is visible onscreen". That's because the user now has the option of selectively removing location access -- even to a foreground service -- based on that criteria. Users can change this setting at any time, even if your app is running.
The Android docs explain that the solution, android:foregroundServiceType="location", was intended for your precise use case: "Google Maps"-like apps, which have a foreground service, but are expected to continue processing location data if the user switches to another app. The technique is called "continuing a user-initiated action", and it allows you to get location updates even after your app is placed in the "background".
(The docs seem to be expanding the definition of the term "background", here. In the past, if you had a foreground service, your app was considered "in the foreground" -- at least for the purposes of task priority, Doze, and so forth. Now it appears that an app is considered "in the background", with respect to location access, if it hasn't been onscreen in the last 30 seconds.)
I am not sure what UI changes (like in the Google Play store) take place when you set a particular foregroundServiceType. Regardless, it seems to me that users are unlikely to object.
OTHER SOLUTIONS FOR ANDROID 10 DEVICES
Alternatively, you could've declared a targetSdkVersion of 28 or less, which will let your app function in a location "compatibility mode".
You also have the option of gaining the ACCESS_BACKGROUND_LOCATION permission, but the docs caution against this:
If your app doesn't require location access while running in the background, it's highly recommended that you not request ACCESS_BACKGROUND_LOCATION...
This approach isn't required, for your use case, because your Activity is used to start your Service; you can be guaranteed that your app has been onscreen at least once before the Service starts getting background location updates. (At least, I assume that that is how the OS determines the start of a "user-initiated action". Presumably, the foregroundServiceType approach won't work if you're starting the Service from a JobScheduler, or a BroadcastReceiver, or something.)
PS: Hang on to that WakeLock code. You're going to need to keep the device awake, if you want to keep getting updates at a steady 10-second pace.
I dont really see any problem in the code, but I am a bit sceptical about START_NOT_STICKY. Try START_STICKY instead.
START_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), then leave it in the
started state but don't retain this delivered intent. Later the system
will try to re-create the service. Because it is in the started state,
it will guarantee to call onStartCommand(Intent, int, int) after
creating the new service instance; if there are not any pending start
commands to be delivered to the service, it will be called with a null
intent object, so you must take care to check for this.
START_NOT_STICKY
If this service's process is killed while it is started (after
returning from onStartCommand(Intent, int, int)), and there are no new
start intents to deliver to it, then take the service out of the
started state and don't recreate until a future explicit call to
Context.startService(Intent). The service will not receive a
onStartCommand(Intent, int, int) call with a null Intent because it
will not be re-started if there are no pending Intents to deliver.
So as you are returning START_NOT_STICKY, if the process is killed, onStartCommand() will not be called again, which is where you initialize both the listener and the locationManager.

Geofence pending intent firing too late when device is in deep sleep (doze) mode

I need to notify user when he is near by given place. I use Geofencing API for this. When i test app on Android emulator with mock location everything works fine. Same for real device with Mock Location. But when I walk and my phone is in deep sleep mode Geofence fires after 5 - 10 min. If i am inside geofences radius and I unlock my phone, open any app which use location my geofence triggers immediately. (Android 5.1, Motorolla moto G 1-st generation)
Below is the way, how I registered my geofence:
public void registerLocation(RegisterAlarmRequestModel data) {
if (isLocationDetectionAllowed() && isConnected) {
GeofencingRequest geofencingRequest = prepareGeofencingRequest(prepareGeofence(data));
PendingIntent pendingIntent = prepareIntent(data.getId());
PendingResult<Status> result = GeofencingApi.addGeofences(
googleApiClient, geofencingRequest, pendingIntent);
Status status = result.await();
if (status.isSuccess())
Log.d("Location", "Geofence " + data.getId() + " has been registered");
}
}
//preparing Geofence Pending Intent which will be triggered
private PendingIntent prepareIntent(int alarmId) {
Intent intent = new Intent(context, LocationRingingService.class);
intent.putExtra(LocationRingingService.KEY_ALARM_ID, alarmId);
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
private GeofencingRequest prepareGeofencingRequest(Geofence geofence) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder()
.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER)
.addGeofence(geofence);
return builder.build();
}
private Geofence prepareGeofence(RegisterAlarmRequestModel data) {
Geofence geofence = new Geofence.Builder()
.setRequestId(String.valueOf(data.getId()))
.setCircularRegion(data.getLatitude(), data.getLongitude(), data.getRadius())
.setLoiteringDelay(100)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.build();
return geofence;
}
For receiving intent I am using IntentService:
#Override
protected void onHandleIntent(Intent intent) {
Log.d("Location", "accepted intent: " + intent.toString());
//database request
}
This is how i have registered my service in manifest:
<service
android:name=".plugin.delivery.ringing.location.service.LocationRingingService"
android:enabled="true"
android:exported="true" />
Update: I need catch the moment when user just entered into geofence as accurate as possible. I have one idea: register geofence with radius greater than need (for example if need 100m radius, register geofence with 200-300m radius). And when user enters into Geophence with larger radius - start service with location udating to improve geofencing precision. And when user just entered - disable location service.
The problem is that when your phone is in deep sleep it is not updating the location accurately. The most accurate way to update location is GPS, and this is also the most battery-intensive. Other ways to update your location, such as using the cellular network, will consume less battery but are also less accurate. By default, geofences want to be really sure you are in the geofence before sending the intent. It is hard to get this sort of accuracy when in deep sleep because the phone is not getting accurate location data.
The reason why the geofence triggers immediately when you unlock your phone and use a location-aware app, is that the app updates the LastLocation, which your geofence sees and then sends the intent. While your phone is in deep sleep the location is not being updated.
With geofences there are also a few settings you can tweak to improve responsiveness. I see you're already using setLoiteringDelay, try playing around with different values , maybe try very small values and see what happens. You could also set a value for setNotificationResponsiveness, which works in a similar way. Doing that should make your fence more responsive, but it may cost more battery life. Also read the API Reference for setLoiteringDelay and setNotificationResponsiveness. Also read the geofence troubleshooting section if you haven't.
You could also increase the size of the geofence, try doubling it and then test. Since your location accuracy is low while in deep sleep, this will make it easier for your phone to be sure that it is inside the geofence, and once it's sure it is inside the geofence it will send the intent.
I hope this helps!
To improve it, Let's perform some checks
1) Use broadcast receiver to get it triggered easily instead of service. And set priority with intent-filter.
For e.g
<receiver
android:name=".youpackage.GeoReceiver"
android:exported="true">
<intent-filter android:priority="999">
<action android:name="yourpackage.ACTION_RECEIVE_GEOFENCE" />
</intent-filter>
</receiver>
And your pending intent will be :
Intent intent = new Intent("yourpackage.ACTION_RECEIVE_GEOFENCE");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
youractivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
2) As your GPS goes in sleep mode, we need to wake it up while creating Geofence. Once you create your geofence, you can start pinging your GPS until you will get ENTER transition. This would must help to get triggering it.
public class GPSService extends Service implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {
GoogleApiClient mGoogleApiClient;
LocationRequest locationRequest;
public GPSService() {
}
#Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
#Override
public void onCreate() {
super.onCreate();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
}
#Override
public void onLocationChanged(Location location) {
Utility.ReadAndWriteData(this, Utility.readFileName(this), "Still Geofence is not triggered!!!");
}
#Override
public void onConnected(Bundle bundle) {
locationRequest = LocationRequest.create();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setFastestInterval(1000);
locationRequest.setInterval(2000);
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,locationRequest,this);
}
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onDestroy() {
super.onDestroy();
if(mGoogleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
}
}
And don't forgot to stop this service immediately when you get ENTER transition or It cause drain battery. This service is only to wake GPS up from sleep mode.

Intent Service not working in doze mode

One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Below is the code:
public class GpsTrackingService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
do{
try{
//make API call here
//then go to sleep for 2 mins
TimeUnit.SECONDS.sleep(120);
} catch(InterruptedException ex){
ex.printStackTrace();
}
} while (preferences.shouldSendGps()); //till the user can send gps.
}
....
}
Manifest
<service android:name=".commons.GpsTrackingService" />
This is working fine when the phone is active. However, whenever the phone goes into doze mode it fails to wake.
Will using alarm manager with WAKE permission solve this?
I have just got the code base and need to fix this within today. It'll be great if someone can help.
As the documentation says:
In Doze mode, the system attempts to conserve battery by restricting
apps' access to network and CPU-intensive services. It also prevents
apps from accessing the network and defers their jobs, syncs, and
standard alarms.
Periodically, the system exits Doze for a brief time to let apps
complete their deferred activities. During this maintenance window,
the system runs all pending syncs, jobs, and alarms, and lets apps
access the network.
In few words, while in Doze mode the system suspends network accesses, ignores Wake Locks, stops acquiring data from sensors, defers AlarmManager jobs to the next Doze maintenance window (which are progressively less frequently called), also WiFi scans, JobScheduler jobs and Sync adapters do not run.
Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 (?) minutes, per app.
And it seems that the Foreground Services are also involved into this "Doze Drama", at least in MarshMellow (M).
To survive in this situation, tons of applications need to be at least rewiewed. Can you imagine a simple mp3 player which stops playing music when the device enters in Doze Mode?
Doze mode starts automatically, when the device is unplugged from the power supply and left on the table for about 1 hour or so, or even earlier when the user clicks the power button to power down the screen, but I think this could depend by the device manufacturer too.
I tried a lot of countermeasures, some of them really hilarious.
At the end of my tests I reached a possible solution:
One possible (and maybe the only) way to have your app running even when the host device is in Doze mode, is basically to have a ForegroundService (even a fake one, doing no jobs at all) running in another process with an acquired partial WakeLock.
What you need to do is basically the following (you could create a simple project to test it):
1 - In your new project, create a new class which extends Application (myApp), or use the
main activity of the new project.
2 - In myApp onCreate() start a Service (myAntiDozeService)
3 - In myAntiDozeService onStartCommand(), create the Notification
needed to start the service as a foreground service, start the
service with startForeground(id, notification) and acquire the
partial WakeLock.
REMEMBER! This will work, but it is just a starting point, because you have to be careful with the "Side Effects" this approach will generate:
1 - Battery drain: The CPU will work for your app forever if you
don't use some strategy and leave the WakeLock always active.
2 - One notification will be always shown, even in the lockscreen,
and this notification cannot be removed by simply swiping it out, it
will be always there until you'll stop the foreground service.
OK, let's do it.
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
#Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
#Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml changes.
In the AndroidManifest.xml add this permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Don't forget to add the name of your app in the <application> tag:
<application
....
android:name=".myApp"
....
And finally add your foreground service running into another process:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
A couple of notes.
In the previous example, the notification created, when clicked,
opens the ActivityMain activity of your test project.
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
but you can use another kind of intent too.
To test it, you have to add some job to be performed into your
ActivityMain.java, for example some repeating alarm (which was
normally stopped when the device falls in Doze Mode), or a ripetitive
network access, or a timed tone played, or.... whatever you want.
Remember that the job performed by the main activity has to run
forever because to test this AntiDoze you need to wait at least 1
hour to be sure the device enters in Doze Mode.
To enter in Doze mode, the device has to be quiet and unplugged, so
you can't test it while you are debugging. Debug your app first,
check that everything is running then stop it, unplug, restart the
app again and leave the device alone and quiet on your desk.
The adb commands suggested by the documentation to simulate Doze
and StandBy modes could and could not give you the right results
(it depends, I suppose, by the device manufacturer, drivers, bla
bla). Please make your tests in the REAL behaviour.
In my first test, I used an AlarmManager and a tone generator to play a tone every 10 minutes just to understand that my app was still active.
And it is still running from about 18 hours, breaking my ears with a loud tone exactly every 10 minutes. :-)
Happy coding!
One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Only have a service running while it is actively delivering value to the user. Sitting around for two minutes, watching the clock tick, is not actively delivering value to the user.
Will using alarm manager with WAKE permission solve this?
That depends on what you mean by "solve this". You can use AlarmManager to request to get control every two minutes so that you can do work. While the device is in Doze mode, you will not actually get control every two minutes, but once per maintenance window.

LocationClient doesn't give callback when screen light goes off but my WakefulThread is running flawlessly as expected

To retrieve fused location in background, I have created a library which is very similar to cwac-locpoll library created by Commonsguy.
Inside PollerThread , I am trying to connect, request and retrieve the locations using LocationClient.
I am able to get connected by receiving callback on onConnected method but I am not able to get callback on onLocationChanged method.so my onTimeout thread executes as per decided interval.
NOTE: This issue happens only when screen light goes off.otherwise it works completely fine.
I suspect there might be bug in new Location Api.
Here is the implementation of my PollerThread,
private class PollerThread extends WakefulThread implements GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener,LocationListener{
private static final String TAG = "PollerThread";
//context
private Context mContext=null;
private LocationClient mLocationClient=null;
private LocationRequest mLocationRequest=null;
private LocationManager locMgr=null;
private Intent intentTemplate=null;
private Handler handler=new Handler();
private Runnable onTimeout = new Runnable() {
#Override
public void run() {
Log.e(TAG, "onTimeout");
//prepare broadcast intent
Intent toBroadcast=new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_ERROR, "Timeout!");
toBroadcast.putExtra(
FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, false);
toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
mLocationClient.getLastLocation());
sendBroadcast(toBroadcast);
//stop the thread
quit();
}
};
PollerThread(Context mContext,LocationRequest mLocationRequest,PowerManager.WakeLock lock, LocationManager locMgr,
Intent intentTemplate) {
super(lock, "LocationPoller-PollerThread");
Log.e(TAG, "PollerThread");
this.mContext=mContext;
this.mLocationRequest=mLocationRequest;
this.locMgr=locMgr;
this.intentTemplate=intentTemplate;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
Log.e(TAG, "onPreExecute");
//setup timeout
setTimeoutAlarm();
//initiate connection
initiateConnection();
}
#Override
protected void onPostExecute() {
super.onPostExecute();
Log.e(TAG, "onPostExecute");
//remove timeout
removeTimeoutAlarm();
//disconnect
initiateDisconnection();
}
/**
* Called when the WakeLock is completely unlocked.
* Stops the service, so everything shuts down.
*/
#Override
protected void onUnlocked() {
Log.e(TAG, "onUnlocked");
stopSelf();
}
private void setTimeoutAlarm() {
Log.e(TAG, "setTimeoutAlarm");
handler.postDelayed(onTimeout, FusedLocationUtils.DEFAULT_TIMEOUT);
}
private void removeTimeoutAlarm()
{
Log.e(TAG, "removeTimeoutAlarm");
handler.removeCallbacks(onTimeout);
}
private void initiateConnection()
{
Log.e(TAG, "initiateConnection");
mLocationClient = new LocationClient(this.mContext, this, this);
mLocationClient.connect();
}
private void initiateDisconnection()
{
Log.e(TAG, "initiateDisconnection");
if(mLocationClient.isConnected())
{
mLocationClient.disconnect();
}
}
#Override
public void onConnected(Bundle arg0) {
Log.e(TAG, "onConnected");
Log.e(TAG, "provider: GPS-"+locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)+" NETWORK-"+locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER));
if (!(locMgr.isProviderEnabled(LocationManager.GPS_PROVIDER)) && !(locMgr.isProviderEnabled(LocationManager.NETWORK_PROVIDER))) {
Log.e(TAG, "both disabled");
//get last location and broadcast it
getLastLocationAndBroadcast();
//stop the thread
quit();
}
else
{
Log.e(TAG, "provider enabled");
//get latest location and broadcast it
getLatestLocationAndBroadcast();
//don't quit from here,quit from onLocationChanged
}
}
#Override
public void onDisconnected() {
Log.e(TAG, "onDisconnected");
// TODO Auto-generated method stub
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.e(TAG, "onConnectionFailed");
// TODO Auto-generated method stub
}
#Override
public void onLocationChanged(Location location) {
Log.e(TAG, "onLocationChanged");
//prepare broadcast intent
Intent toBroadcast=new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_LOCATION, location);
sendBroadcast(toBroadcast);
//stop further updates
stopUpdates();
//stop the thread
quit();
}
private void getLatestLocationAndBroadcast() {
Log.e(TAG, "getLatestLocationAndBroadcast");
if(mLocationClient.isConnected() && servicesConnected())
{
Log.e(TAG, "going to request updates");
Log.e(TAG, "lockStatic.isHeld(): "+lockStatic.isHeld());
mLocationClient.requestLocationUpdates(mLocationRequest, this);
}
else
{
Log.e(TAG, "not going to request updates");
}
}
private void stopUpdates() {
Log.e(TAG, "stopUpdates");
if(servicesConnected())
{
Log.e(TAG,getString(R.string.location_updates_stopped));
mLocationClient.removeLocationUpdates(this);
}
else
{
Log.e(TAG,"can't do:"+getString(R.string.location_updates_stopped));
}
}
private void getLastLocationAndBroadcast() {
Log.e(TAG, "getLastLocationAndBroadcast");
if(mLocationClient.isConnected() && servicesConnected())
{
Log.e(TAG, "going to get last location: "+mLocationClient.getLastLocation());
Intent toBroadcast = new Intent(intentTemplate);
toBroadcast.putExtra(FusedPoller.EXTRA_ERROR,
"Location Provider disabled!");
toBroadcast.putExtra(
FusedPoller.EXTRA_ERROR_PROVIDER_DISABLED, true);
toBroadcast.putExtra(FusedPoller.EXTRA_LASTKNOWN,
mLocationClient.getLastLocation());
sendBroadcast(toBroadcast);
}
else
{
Log.e(TAG, "not going to get last location");
}
}
}
and servicesConnected method implementation,
/**
* Verify that Google Play services is available before making a request.
*
* #return true if Google Play services is available, otherwise false
*/
private boolean servicesConnected() {
Log.e(TAG, "servicesConnected");
// Check that Google Play services is available
int resultCode =
GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
// If Google Play services is available
if (ConnectionResult.SUCCESS == resultCode) {
// In debug mode, log the status
Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_available));
// Continue
return true;
// Google Play services was not available for some reason
} else {
// Display an error dialog
Log.d(FusedLocationUtils.APPTAG, getString(R.string.play_services_unavailable));
Toast.makeText(this, getString(R.string.play_services_unavailable), Toast.LENGTH_SHORT).show();
return false;
}
}
If you want to listen to frequent location updates in the background (e.g., every second), you should be running your code inside a Service:
http://developer.android.com/reference/android/app/Service.html
Activities can be ended by the Android platform at any point in time in which they are not in the foreground.
When using a Service, I would recommend having the Service implement the LocationListener directly, and not a Thread inside the Service. For example, use:
public class LocListener extends Service implements com.google.android.gms.location.LocationListener, ...{
I've used this design of implementing the LocationListener directly on the Service with the LocationClient and fused location provider in my GPS Benchmark app and I can confirm that this works even when the screen is off and the app is running in the background.
If you want to listen to occasional location updates in the background (e.g., every minute) using the fused location provider, a better design is to use PendingIntents, using the LocationClient.requestLocationUpdates(Location Request, PendingIntent callbackIntent) method:
https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html#requestLocationUpdates(com.google.android.gms.location.LocationRequest,%20android.app.PendingIntent)
From the above Android doc:
This method is suited for the background use cases, more specifically for receiving location updates, even when the app has been killed by the system. In order to do so, use a PendingIntent for a started service. For foreground use cases, the LocationListener version of the method is recommended, see requestLocationUpdates(LocationRequest, LocationListener).
Any previous LocationRequests registered on this PendingIntent will be replaced.
Location updates are sent with a key of KEY_LOCATION_CHANGED and a Location value on the intent.
See the Activity Recognition example for a more detailed description of using PendingIntents to get updates while running in the background:
https://developer.android.com/training/location/activity-recognition.html
Modified excerpts from this documentation are below, changed by me to be specific to location updates.
First declare the Intent:
public class MainActivity extends FragmentActivity implements
ConnectionCallbacks, OnConnectionFailedListener {
...
...
/*
* Store the PendingIntent used to send location updates
* back to the app
*/
private PendingIntent mLocationPendingIntent;
// Store the current location client
private LocationClient mLocationClient;
...
}
Request updates as you currently are, but this time pass in the pending intent:
/*
* Create the PendingIntent that Location Services uses
* to send location updates back to this app.
*/
Intent intent = new Intent(
mContext, LocationIntentService.class);
...
//Set up LocationRequest with desired parameter here
...
/*
* Request a PendingIntent that starts the IntentService.
*/
mLocationPendingIntent =
PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
/*
* Request location updates
*/
mLocationClient.requestLocationUpdates(mLocationRequest, callbackIntent);
Handle Location Updates
To handle the Intent that Location Services sends for each update interval, define an IntentService and its required method onHandleIntent(). Location Services sends out ... updates as Intent objects, using the the PendingIntent you provided when you called requestLocationUpdates(). Since you provided an explicit intent for the PendingIntent, the only component that receives the intent is the IntentService you're defining.
Define the class and the required method onHandleIntent():
/**
* Service that receives Location updates. It receives
* updates in the background, even if the main Activity is not visible.
*/
public class LocationIntentService extends IntentService {
...
/**
* Called when a new location update is available.
*/
#Override
protected void onHandleIntent(Intent intent) {
Bundle b = intent.getExtras();
Location loc = (Location) b.get(LocationClient.KEY_LOCATION_CHANGED);
Log.d(TAG, "Updated location: " + loc.toString());
}
...
}
IMPORTANT - to be as efficient as possible, your code in onHandleIntent() should return as quickly as possible to allow the IntentService to shut down. From IntentService docs:
http://developer.android.com/reference/android/app/IntentService.html#onHandleIntent(android.content.Intent)
This method is invoked on the worker thread with a request to process. Only one Intent is processed at a time, but the processing happens on a worker thread that runs independently from other application logic. So, if this code takes a long time, it will hold up other requests to the same IntentService, but it will not hold up anything else. When all requests have been handled, the IntentService stops itself, so you should not call stopSelf().
My understanding of the IntentService design is that you can spawn Threads inside onHandleIntent() to avoid blocking other location updates via platform calls to onHandleIntent(), just be aware that the Service will continue to run until all the running threads terminate.
I've spent days trying to get WiFi and cell-based locations with locked screen with Android 6.0 on Nexus 6.
And looks like the native android location service simple does not allow to do it.
Once device got locked it still collects location update events for 10-15 minutes then stops to providing any of location updates.
In my case the solution was to switch from native Android location service to Google Play Services wrapper called com.google.android.gms.location: https://developers.google.com/android/reference/com/google/android/gms/location/package-summary
Yes, I know that some of Android devices lack of GMS, but for my application this is the only solution to perform.
It does not stop sending location updates even when in the background and device screen is locked.
Me personally prefer RxJava library to wrap this service into a stream (examples included): https://github.com/mcharmas/Android-ReactiveLocation

Android: How to periodically send location to a server

I am running a Web service that allows users to record their trips (kind of like Google's MyTracks) as part of a larger app. The thing is that it is easy to pass data, including coords and other items, to the server when a user starts a trip or ends it. Being a newbie, I am not sure how to set up a background service that sends the location updates once every (pre-determined) period (min 3 minutes, max 1 hr) until the user flags the end of the trip, or until a preset amount of time elapses.
Once the trip is started from the phone, the server responds with a polling period for the phone to use as the interval between updates. This part works, in that I can display the response on the phone, and my server registers the user's action. Similarly, the trip is closed server-side upon the close trip request.
However, when I tried starting a periodic tracking method from inside the StartTrack Activity, using requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener) where minTime is the poll period from the server, it just did not work, and I'm not getting any errors. So it means I'm clueless at this point, never having used Android before.
I have seen many posts here on using background services with handlers, pending intents, and other things to do similar stuff, but I really don't understand how to do it. I would like the user to do other stuff on the phone while the updates are going on, so if you guys could point me to a tutorial that shows how to actually write background services (maybe these run as separate classes?) or other ways of doing this, that would be great.
I recently wrote one of these and decided it is not a good idea to leave a background service running. It will probably be shut down by the operating system anyway, or it could be. What I did was use a filter for the boot intent and then set an alarm using the alarm manager so that my app was restarted at regular intervals, and then it sent the data. You can find good info on services and the alarm manager in the Android documentation.
First I created a broadcast receiver that simply starts my service when an internet connection is opened (I'm only interested if there is a connection - you might want to filter for the boot event as well). The launch receiver must be short-lived, so just start your service:
public class LaunchReceiver extends BroadcastReceiver {
public static final String ACTION_PULSE_SERVER_ALARM =
"com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM";
#Override
public void onReceive(Context context, Intent intent) {
AppGlobal.logDebug("OnReceive for " + intent.getAction());
AppGlobal.logDebug(intent.getExtras().toString());
Intent serviceIntent = new Intent(AppGlobal.getContext(),
MonitorService.class);
AppGlobal.getContext().startService(serviceIntent);
}
}
In the manifest I have:
<receiver
android:name="LaunchReceiver"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
<intent-filter>
<action android:name="com.proofbydesign.homeboy.ACTION_PULSE_SERVER_ALARM" />
</intent-filter>
</receiver>
Notice how I have a filter for my own alarm, which is what allows me to shut the service and have it restarted after it's done its work.
The top of my monitor service looks like:
public class MonitorService extends Service {
private LoggerLoadTask mTask;
private String mPulseUrl;
private HomeBoySettings settings;
private DataFile dataFile;
private AlarmManager alarms;
private PendingIntent alarmIntent;
private ConnectivityManager cnnxManager;
#Override
public void onCreate() {
super.onCreate();
cnnxManager = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
alarms = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intentOnAlarm = new Intent(
LaunchReceiver.ACTION_PULSE_SERVER_ALARM);
alarmIntent = PendingIntent.getBroadcast(this, 0, intentOnAlarm, 0);
}
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
// reload our data
if (mPulseUrl == null) {
mPulseUrl = getString(R.string.urlPulse);
}
AppGlobal.logDebug("Monitor service OnStart.");
executeLogger();
}
executeLogger starts an asyncTask, which is probably me being excessively cautious (this was only my third Android app). The asyncTask grabs the GPS data, sends it to the internet and finally sets the next alarm:
private void executeLogger() {
if (mTask != null
&& mTask.getStatus() != LoggerLoadTask.Status.FINISHED) {
return;
}
mTask = (LoggerLoadTask) new LoggerLoadTask().execute();
}
private class LoggerLoadTask extends AsyncTask<Void, Void, Void> {
// TODO: create two base service urls, one for debugging and one for live.
#Override
protected Void doInBackground(Void... arg0) {
try {
// if we have no data connection, no point in proceeding.
NetworkInfo ni = cnnxManager.getActiveNetworkInfo();
if (ni == null || !ni.isAvailable() || !ni.isConnected()) {
AppGlobal
.logWarning("No usable network. Skipping pulse action.");
return null;
}
// / grab and log data
} catch (Exception e) {
AppGlobal.logError(
"Unknown error in background pulse task. Error: '%s'.",
e, e.getMessage());
} finally {
// always set the next wakeup alarm.
int interval;
if (settings == null
|| settings.getPulseIntervalSeconds() == -1) {
interval = Integer
.parseInt(getString(R.string.pulseIntervalSeconds));
} else {
interval = settings.getPulseIntervalSeconds();
}
long timeToAlarm = SystemClock.elapsedRealtime() + interval
* 1000;
alarms.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToAlarm,
alarmIntent);
}
return null;
}
}
I notice that I am not calling stopSelf() after setting the alarm, so my service will sit around doing nothing unless shut down by the op sys. Since I am the only user of this app, that doesn't matter but for a public app, the idea is you set the alarm for the next interval then stopSelf to close down.
Update See the comment from #juozas about using 'alarms.setRepeating()'.
You need to create a separate class that is a subclass of the Service class.
Service Documentation
Your primary application should can call startService and stopService to start up the background process. Theres also some other useful calls in the context class to manage the service:
Context Documentation
I agree with Rob Kent, and in additional I think could be beter to extends WakefulBroadcastReceiver in your BroadcastReceiver and use it's static method startWakefulService(android.content.Context context,android.content.Intent intent), because it garanted your service will not shut by os.
public class YourReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent service = new Intent(context, YourService.class);
startWakefulService(context, service);
}
}
Official documentation

Categories

Resources