Google Play Services' Fused Location Provider Api lets you request location updates with location listeners or pending intents. I can successfully request location updates with the location listener but I have been struggling to replicate the same behaviour with pending intents. For the latter, I launch an intent service which handles the location data. What I have noticed during testing is that the location updates correspond with the interval I set in the location request. However, as time goes by the interval between updates increases tremendously even though the interval in the location request has remained constant. I have noticed this behaviour on several occasions with multiple devices. Does anyone have an idea what could be going on?
Foreground location tracking
protected void requestLocationUpdates()
{
mIsTracking = true;
LocationRequest locationRequest = mLocationRequests.get(mPreviousDetectedActivity.getType());
if (locationRequest != null)
{
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, locationRequest, this);
}
}
#Override
public void onLocationChanged(Location location)
{
Log.i(TAG, "onLocationChanged");
handleLocationChanged(location);
}
Background location tracking
protected void requestLocationUpdates()
{
LocationRequest locationRequest = mLocationRequests.get(mPreviousDetectedActivity.getType());
if (locationRequest != null)
{
mResultCallabackMessage = "Request location updates ";
PendingIntent pendingIntent = getLocationPendingIntent();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
locationRequest, pendingIntent).setResultCallback(this);
}
}
protected PendingIntent getLocationPendingIntent()
{
if (mLocationPendingIntent != null) return mLocationPendingIntent;
Intent intent = new Intent(this, LocationUpdatesIntentService.class);
mLocationPendingIntent = PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return mLocationPendingIntent;
}
public class LocationUpdatesIntentService extends IntentService
{
public LocationUpdatesIntentService()
{
// Use the TAG to name the worker thread.
super(TAG);
}
#Override
public void onCreate()
{
super.onCreate();
}
#Override
protected void onHandleIntent(Intent intent)
{
Bundle bundle = intent.getExtras();
Location location = (Location) bundle.get(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
handleLocationUpdates(location);
}
}
Any help is greatly appreciated.
It could be because some other application in the device must have requested for more frequent updates. Please refer this link for more details
This interval is inexact. You may not receive updates at all (if no
location sources are available), or you may receive them slower than
requested. You may also receive them faster than requested (if other
applications are requesting location at a faster interval). The
fastest rate that that you will receive updates can be controlled with
setFastestInterval(long)
I'm having the same problem too, but as #Shiv said you should be testing different parameters:
mClient.requestLocationUpdates(LocationRequest.create(), mLocationIntent)
try this:
mClient.requestLocationUpdates(createLocationRequest(), mLocationIntent)
private LocationRequest createLocationRequest() {
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(UPDATE_INTERVAL);
locationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
locationRequest.setMaxWaitTime(MAX_WAIT_TIME);
return locationRequest;
}
Related
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.
UPDATE: I'm getting this error, like it's explained in this questions (1 and 2), but according to this answer, this is only an internal log and shouldn't have impact on the functionality
V/GoogleSignatureVerifier: com.google.android.gms signature not valid
ORIGINAL QUESTION:
I have to implement different features related to the user location that have to work even if the app is killed. To achieve that, I'm using a service. I have issues when I try to track the user, because it seems that I have a problem requesting the location updates.
Here's the most relevant parts of my service:
DISCLAIMER: The code have been simplified for the reading sake. All the methods related to the service binding, along with several callbacks, have been removed because aren't part of the problem
public class LocationService extends RoboService implements LocationListener {
private static final String TAG = "LocationService";
private static final long UPDATE_INTERVAL_IN_MILLISECONDS = TimeUnit.SECONDS.toMillis(5);
private static final long FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = TimeUnit.SECONDS.toMillis(1);
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
#Override
public void onCreate() {
super.onCreate();
if (mGoogleApiClient == null) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}
public Location getCurrentLocation() {
ConnectionResult connectionResult = mGoogleApiClient.blockingConnect();
if (connectionResult.isSuccess()) {
return LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
}
return null;
}
public void startLocationUpdates() {
ConnectionResult connectionResult = mGoogleApiClient.blockingConnect();
if (connectionResult.isSuccess()) {
Log.d(TAG, "startLocationUpdates: Posting request");
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
Log.d(TAG, "startLocationUpdates: Request posted");
}
}
#Override
public void onLocationChanged(Location newLocation) {
System.out.println("LocationService.onLocationChanged");
}
}
I'm using mGoogleApiClient.blockingConnect() because I can't use the connection callbacks due to architecture constrains, but I don't have any problems when I try to get the current location.
When I call startLocationUpdates() the execution seems to be lost when I request the location updates and neither Log.d(TAG, "startLocationUpdates: Request posted") nor onLocationChanged(Location newLocation) are ever called.
Here's the logcat:
05-23 11:42:47.509 13547-13547/com.example.location D/MapPresenterImpl: onRouteAdded: Starting route tracking
05-23 11:42:47.512 13547-13950/com.example.location D/LocationService: startLocationUpdates: Posting request
05-23 11:43:28.115 13547-13557/com.example.location W/art: Suspending all threads took: 7.530ms
My code is basically copied from the Android examples. I downloaded the example source code and I have it working correctly. The two only differences between my code and the example's is mine being inside a service and using mGoogleApiClient.blockingConnect(), but I don't know how this could affect in this way.
Someone have ever faced this kind of behaviour? I'm truly lost and I would preciate any help.
WORKAROUND:
At the end I solved this problem using PendingIntent instead of the Callback approach. I still would be glad to know why this happened, but if someone faces this problem and doesn't know how to proceed, here's my workaround.
Method to start the location updates:
public void startLocationTracking(long routeId) {
ConnectionResult connectionResult = mGoogleApiClient.blockingConnect();
if (connectionResult.isSuccess()) {
Intent intent = new Intent(this, TrackingService.class);
PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
LocationRequest request = new LocationRequest();
request.setInterval(UPDATE_INTERVAL_IN_MILLISECONDS);
request.setFastestInterval(FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS);
request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, pi);
}
}
Tracking service:
public class TrackingService extends IntentService {
public TrackingService() {
super("TrackingService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (LocationResult.hasResult(intent)) {
LocationResult locationResult = LocationResult.extractResult(intent);
Location location = locationResult.getLastLocation();
// Do whatever you need with the location
}
}
}
Using this method I faced another weird problem. If you put some extra in the Intent used to build the PendingIntent, when TrackingService is called there isn't any location in the Intent extras, only what was added when the Intent was created. My workaround for this was using the SharedPreferences to pass an id that I needed to have, but I'm not really convinced by this patch. This issue is discussed in this question.
Objective: To save current locations in Service in database after exact 15 min with in service (using less battery).I use these location at various points in my app.
locationrequest = LocationRequest.create();
locationrequest.setInterval(5*60000);
locationrequest
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
Problem: I'm using the above code does not request location according to set interval value.Although, I'm aware that This interval is inexact. You may not receive updates at all, or you may receive them slower than requested. You may also receive them faster than requested. Sometimes, the location is updated after 1 min , I don't want to waste processing and battery to get locations at small intervals.
public class LoginActivity extends Activity implements OnClickListener
,
GooglePlayServicesClient.ConnectionCallbacks,GooglePlayServicesClient.OnConnectionFailedListener,LocationListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_screen);
///my code
mIntentService = new Intent(LoginActivity.this,LocationService.class);
mIntentService.putExtra("time",String.valueOf(System.currentTimeMillis()) );
mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);
int resp =GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
if(resp == ConnectionResult.SUCCESS){
locationclient = new LocationClient(this,this,this);
locationclient.connect();
}
else{
Toast.makeText(this, "Google Play Service Error " + resp, Toast.LENGTH_LONG).show();
}
#Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
// TODO Auto-generated method stub
}
#Override
public void onConnected(Bundle arg0) {
// TODO Auto-generated method stub
Log.i("fused", " onConnected " );
// mIntentService = new Intent(LoginActivity.this,LocationService.class);
// mPendingIntent = PendingIntent.getService(LoginActivity.this, 1, mIntentService, 0);
locationrequest = LocationRequest.create();
locationrequest.setInterval(5*60000);
// locationrequest
// .setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, mPendingIntent);
// locationrequest = LocationRequest.create();
// locationrequest.setInterval(1000);//??
// locationclient.requestLocationUpdates(locationrequest, this);
}
#Override
public void onDisconnected() {
// TODO Auto-generated method stub
}
}
LocationService
public class LocationService extends IntentService {
private String TAG = this.getClass().getSimpleName();
public LocationService() {
super("Fused Location");
}
public LocationService(String name) {
super("Fused Location");
}
#Override
protected void onHandleIntent(Intent intent) {
// Log.i("fused", "onHandleIntent LocationService");
Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
if(location !=null){
String time= intent.getStringExtra("time");
Log.i("fused", "onHandleIntent LocationService " +time+"---"+ location.getLatitude() + "," + location.getLongitude());
updateTransientLocation(getApplicationContext(), location);
}
}
Also, I need to save these locations periodically in database in background only and hence cannot use requestLocationUpdates without pending intent to service.
I have refered to this for the code
Thanks.
EDIT -SOLUTION This is how my problem was solved
Code in Activity
Intent myIntent = new Intent(context,LocationReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, myIntent, 0);
alarmMgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
// 120000,pi);
I removed the location Service class and added location receiver
LocationReceiver
public class LocationReceiver extends BroadcastReceiver implements
GooglePlayServicesClient.ConnectionCallbacks,
GooglePlayServicesClient.OnConnectionFailedListener, LocationListener {
SharedPreferences prefs = null;
LocationClient locationclient = null;
Context contxt;
/** For location poller NO LONGER IN USE **/
#Override
public void onReceive(Context context, Intent intent) {
contxt=context;
//Log.i("locationreciever", "in location rec");
Log.i("fused", "in location rec");
int resp = GooglePlayServicesUtil
.isGooglePlayServicesAvailable(context);
if (resp == ConnectionResult.SUCCESS) {
locationclient = new LocationClient(context, this, this);
locationclient.connect();
} else {
Log.i("fused", "loc client Google Play Service Error");
}
}
#Override
public void onLocationChanged(Location location) {
Log.i("fused", " onLocationChanged Location Request :" + location.getLatitude() + "," + location.getLongitude());
updateTransientLocation(contxt, location);
if (locationclient != null) {
if (locationclient.isConnected()) {
locationclient.removeLocationUpdates(this);
locationclient.disconnect();
}
}
}
#Override
public void onConnectionFailed(ConnectionResult arg0) {
Log.i("fused", "loc client connection failed");
}
#Override
public void onConnected(Bundle arg0) {
Log.i("fused", "loc client onConnected");
LocationRequest locationrequest = LocationRequest.create();
locationrequest
.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
locationclient.requestLocationUpdates(locationrequest, this);
}
#Override
public void onDisconnected() {
Log.i("fused", "loc client disconnected");
}
}
The best solution would be to use your current approach. You're tell the OS that you don't need locations more often, but something else might be requesting locations, in which case you might as well just accept it, now that the phone has already woken up to get a GPS fix and broadcast it to every process that's interested in a location. This way, your application may actually never have to turn on the GPS, because you're basically just using a location fix that was requested by another process more often that every 15 minutes. The keyword to search for here is the new fused location provider.
If you insist on getting a location exactly every 15 minutes, you can, instead of scheduling a location request, use an AlarmManager to schedule a job to run every 15 minutes. In your alarm manager, you can then immediately request a new single location, and then completely stop requesting new locations until your job is scheduled to run again. If you go down this route, you'll likely run into problems with your service ending before you get a result, because of the asynchronous nature of the location service. Therefore, you want to poll for a location in your alarm manager. You can use a project like CWAC LocationPoller for that
The documentation has examples of how to schedule recurring events:
https://developer.android.com/training/scheduling/alarms.html
Depending on your need, you should be think about the fact that a location may not be available every 15 minutes. Maybe the user is outside of GPS/wifi/phone range. So it may or may not be beneficial to start a task a bit early, or more often, to make sure you have a reasonable fix after your 15 minute window has elapsed.
With all that said, here's the code snippet you're actually interested in to solve your specific problem (taken directly from the CWAC locationpoller site):
1. Create a recurring alarm manager
mgr=(AlarmManager)getSystemService(ALARM_SERVICE);
Intent i=new Intent(this, LocationPoller.class);
Bundle bundle = new Bundle();
LocationPollerParameter parameter = new LocationPollerParameter(bundle);
parameter.setIntentToBroadcastOnCompletion(new Intent(this, LocationReceiver.class));
// try GPS and fall back to NETWORK_PROVIDER
parameter.setProviders(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER});
parameter.setTimeout(60000);
i.putExtras(bundle);
pi=PendingIntent.getBroadcast(this, 0, i, 0);
mgr.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
PERIOD,
pi);
2. Create a BroadcastReceiver to receive your location data
Bundle b=intent.getExtras();
LocationPollerResult locationResult = new LocationPollerResult(b);
Location loc=locationResult.getLocation();
String msg;
if (loc==null) {
loc=locationResult.getLastKnownLocation();
if (loc==null) {
msg=locationResult.getError();
}
else {
msg="TIMEOUT, lastKnown="+loc.toString();
}
}
else {
msg=loc.toString();
}
if (msg==null) {
msg="Invalid broadcast received!";
}
From http://developer.android.com/reference/android/app/AlarmManager.html#setRepeating%28int,%20long,%20long,%20android.app.PendingIntent%29
as of API 19, all repeating alarms are inexact. If your application needs precise delivery times then it must use one-time exact alarms, rescheduling each time as described above. Legacy applications whose targetSdkVersion is earlier than API 19 will continue to have all of their alarms, including repeating alarms, treated as exact.
So you will have to do something like this:
public void startTheClock(int interval) {
Intent pingerIntent = new Intent(this, findLoc.class);
pingerIntent.setAction("start_clock");
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this.getApplicationContext(),
0,
pingerIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarms = (AlarmManager) this.getSystemService(
Context.ALARM_SERVICE);
alarms.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + interval,
pendingIntent);
}
And in the class that captures that intent (in this example, findLoc.java):
public class findLoc extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
callMethodThatSearchesForLocation();
startTheClock(INTERVAL);
}
}
Where interval is a constant in miliseconds.
NOTE: I actually had some problems with that because it displayed an error on setExact(..) since my minimum SDK did not support this. Which is a bit of a paradox if you want the same behaviour on SDK lower than 19 and higher or equal to 19.
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
I'm new to Android and I'm having the following problem. I'm writing a sample application where I have an intent service that first checks all location providers to get the last known location. If none of the last known locations provides an accurate (or timely) enough location then the location manager's requestLocationUpdates method is called with a BroadcastReceiver intent. Each time the broadcast receiver's onReceive method is called it should check the location to see if it is accurate and/or timely enough. I also have a TimerTask in the intent service that eventually goes off and should check to see if an accurate and/or timely enough location update has been obtained. The problem I'm having is that I don't know how to get the location data obtained in the broadcast receiver back to the intent service. Seems like this should be an easy thing to do but I've been agonizing over this for days. The only way I can think to do it is to write the data to an SQLite db in the broadcast receiver and then read those records back in the intent service, but this seems unnecessarily complicated. Does anyone know what the right way is to get the data back to the intent service? Should I even be using a broadcast receiver for requestLocationUpdates? Is there an easier way to do this? Here is the code
public class GetLocationService extends IntentService {
public GetLocationService() {
super("something");
}
LocationManager locationManager;
long maxFixLateness;
float maxFixPosUncertainty;
boolean usableLocObtained;
Location bestLoc = null;
float bestLocScore = 0;
Intent locChangeI;
PendingIntent pLocChangeI;
#Override
final protected void onHandleIntent(Intent intent) {
maxFixLateness = 30000;
maxFixPosUncertainty = 30;
long curTime = System.currentTimeMillis();
LocationManager locationManager = (LocationManager) this
.getSystemService(Context.LOCATION_SERVICE);
// Check for a usable location fix
List<string> matchingProviders = locationManager.getAllProviders();
for (String provider : matchingProviders) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
// ...some code to check if the location is accurate or timely
// enough
}
}
if (bestLoc == null) {
locChangeI = new Intent(this, HandleLocationUpdateReceiver.class);
pLocChangeI = PendingIntent.getBroadcast(this, 0, locChangeI,
PendingIntent.FLAG_UPDATE_CURRENT);
usableLocObtained = false;
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, 0, 0, pLocChangeI);
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 0, 0, pLocChangeI);
// Call the timer that will periodically check to see if a usable
// location has been obtained.
new LocFixCheckTimer(60000, 30, 1000);
}
}
private class LocFixCheckTimer {
Timer timer;
long numChecks;
public LocFixCheckTimer(long initSearchTime, long maxRechecks,
long recheckFreq) {
numChecks = maxRechecks;
timer = new Timer();
// Wait 2 seconds before checking for a fix again
timer.schedule(new CheckLocTask(), initSearchTime, recheckFreq);
}
class CheckLocTask extends TimerTask {
public void run() {
if (numChecks > 0) {
if (usableLocObtained == true) {
// I want to use the location data obtained from the
// HandleLocationUpdateReceiver's onReceive method
// but I don't how to get that data here.
}
} else {
// Cancel the timer. We've timed-out on searching
// for a usable location fix
timer.cancel();
}
--numChecks;
}
}
}
}
Here is the broadcast receiver:
public class HandleLocationUpdateReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Location loc = (Location) intent.getExtras().get(LocationManager.KEY_LOCATION_CHANGED);
if (loc != null)
{
double lat = loc.getLatitude();
double lon = loc.getLongitude();
// Do some checking to see how accurate and timely the location is
// here and somehow get it back to the intent service.
}
}
}
Thanks for the help!
Use a listener for sending back data to your activity or service. It is provided in this link