Im trying to implement "Fused Location".
And i have a location service that uses onHandleIntent..
my question is this : can I send get the location information latitude, longitude in the main activity from onHandleIntent???
Im calling this service from mainactivity.
like this :
mIntentService = new Intent(this,LocationService.class);
mPendingIntent = PendingIntent.getService(this, 1, mIntentService, 0);
And my Service Location is this :
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) {
Location location = intent.getParcelableExtra(LocationClient.KEY_LOCATION_CHANGED);
if(location !=null){
Log.i(TAG, "onHandleIntent " + location.getLatitude() + "," + location.getLongitude());
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Builder noti = new NotificationCompat.Builder(this);
noti.setContentTitle("Fused Location");
noti.setContentText(location.getLatitude() + "," + location.getLongitude());
noti.setSmallIcon(R.drawable.ic_launcher);
notificationManager.notify(1234, noti.build());
}
}
}
Use LocalBroadcastManager.
By the android documentation:
Helper to register for and send broadcasts of Intents to local objects within your process. This is has a number of advantages over sending global broadcasts with sendBroadcast(Intent):
You know that the data you are broadcasting won't leave your app, so don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts to your app, so you don't need to worry about having security holes they can exploit.
It is more efficient than sending a global broadcast through the system.
Check these:
http://www.intertech.com/Blog/using-localbroadcastmanager-in-service-to-activity-communications/
how to use LocalBroadcastManager?
Related
This is how I add my geofences:
public void setGeofenceRequest(Location location) {
if (geofences == null) {
geofences = new ArrayList<Geofence>();
}
geofences.add(new Geofence.Builder()
.setRequestId("3")
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT)
.setCircularRegion(
location.getLatitude(), location.getLongitude(), PSLocationService.getInstance(context).kPSGeofencingDistanceMedium)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build());
Intent intent = new Intent(context, ReceiveTransitionsBroadcastReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
if (geofences.size() > 0) {
LocationServices.GeofencingApi.addGeofences(mLocationClient, geofences, pi);
Log.i("", "geof autopilot2 will set geofence for autopilot-3");
}
}
And this is my BroadcastReceiver. Where I should receive them:
public class ReceiveTransitionsBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context ctx, Intent intent) {
Log.i("","autopilot valid geof on receive transisionts broadcast receiver");
PSMotionService.getInstance(ctx).buildGoogleApiClient();
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
int transitionType = geofencingEvent.getGeofenceTransition();
Location geofenceCenter = PSApplicationClass.getInstance().pref.getGeoCenter(ctx);
if(geofencingEvent.getTriggeringLocation() != null) {
if (geofenceCenter != null) {
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver TRIGGERING LOCATION: " + geofencingEvent.getTriggeringLocation().toString() + " / GEOFENCE CENTER: " + geofenceCenter.getLatitude() + ", " + geofenceCenter.getLongitude(), "D", Constants.TRACKER);
} else
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver TRIGGERING LOCATION: " + geofencingEvent.getTriggeringLocation().toString(), "D", Constants.TRACKER);
}else Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver ERROR => TRIGGERING LOCATION NULL", "D", Constants.TRACKER);
if(transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) {
List<Geofence> triggerList = geofencingEvent.getTriggeringGeofences();
for (Geofence geofence : triggerList) {
Log.i("", "geof is s receive transition broadcast receiver " + transitionType + " GPS zone " + geofence.getRequestId());
if(geofence.getRequestId().contentEquals("3")) {
Log.i("", "geof autopilot2 ENTERED GEOFENCE will start pilot with first location");
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver check to see if should start pilot", "T", Constants.TRACKER);
PSLocationService.getInstance(ctx).fastGPS = -1;
PSLocationService.getInstance(ctx).RequestLocationUpdates();
if(PSTrip.getActiveTrip() != null) {
PSLocationService.getInstance(ctx).removeAutoPilotGeofence();
}else PSMotionService.getInstance(ctx).checkinTime = System.currentTimeMillis() / 1000;
}
}
}
}
}
Now usually it works, but not always. I would say that only about 75% of the time it should work, the geofence events are actually called. I feel like the more time since I've set the geofence, the less likely it will be to be called.
Why is this happening? Is the triggering event also being dismissed, when the app is cleaned by the garbage collector?
How can I make it so that my geofence is always being called, when the case?
EDIT:
This is my defaultConfig:
defaultConfig {
minSdkVersion 15
targetSdkVersion 23
ndk {
moduleName "ndkVidyoSample"
}
}
I changed from a Broadcast Receiver to a IntentService:
public class PSGeofenceTransitionsIntentService extends IntentService {
private static ActivityManager manager;
private static PSGeofenceTransitionsIntentService instance;
private GeofencingClient mGeofencingClient;
Context context;
private PendingIntent mGeofencePendingIntent;
public static boolean isMyServiceRunning(Class<?> serviceClass) {
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
public static PSGeofenceTransitionsIntentService getInstance(Context context) {
if (instance == null) {
// Create the instance
instance = new PSGeofenceTransitionsIntentService(context);
}
if (!isMyServiceRunning(PSGeofenceTransitionsIntentService.class)) {
Intent bindIntent = new Intent(context, PSGeofenceTransitionsIntentService.class);
context.startService(bindIntent);
}
// Return the instance
return instance;
}
public PSGeofenceTransitionsIntentService() {
super("GeofenceTransitionsIntentService");
}
public PSGeofenceTransitionsIntentService(Context context) {
super("GeofenceTransitionsIntentService");
mGeofencingClient = LocationServices.getGeofencingClient(context);
manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
instance = this;
this.context = context;
}
protected void onHandleIntent(Intent intent) {
Log.i("", "autopilot valid geof on receive transisionts broadcast receiver");
PSMotionService.getInstance(context).buildGoogleApiClient();
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
int transitionType = geofencingEvent.getGeofenceTransition();
Location geofenceCenter = PSApplicationClass.getInstance().pref.getGeoCenter(context);
if (geofencingEvent.getTriggeringLocation() != null) {
if (geofenceCenter != null) {
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver TRIGGERING LOCATION: " + geofencingEvent.getTriggeringLocation().toString() + " / GEOFENCE CENTER: " + geofenceCenter.getLatitude() + ", " + geofenceCenter.getLongitude(), "D", Constants.TRACKER);
} else
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver TRIGGERING LOCATION: " + geofencingEvent.getTriggeringLocation().toString(), "D", Constants.TRACKER);
} else
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver ERROR => TRIGGERING LOCATION NULL", "D", Constants.TRACKER);
if (transitionType == Geofence.GEOFENCE_TRANSITION_EXIT) {
List<Geofence> triggerList = geofencingEvent.getTriggeringGeofences();
for (Geofence geofence : triggerList) {
Log.i("", "geof is s receive transition broadcast receiver " + transitionType + " GPS zone " + geofence.getRequestId());
if (geofence.getRequestId().contentEquals("3")) {
Log.i("", "geof autopilot2 ENTERED GEOFENCE will start pilot with first location");
Utils.appendLog("GEOFENCE ENTERED ReceiveTransitionsBroadcastReceiver check to see if should start pilot", "T", Constants.TRACKER);
PSLocationService.getInstance(context).isLocationRequestsOn = -1;
PSLocationService.getInstance(context).RequestLocationUpdates();
if (PSTrip.getActiveTrip() != null) {
removeAutoPilotGeofence();
} else
PSMotionService.getInstance(context).checkinTime = System.currentTimeMillis() / 1000;
}
}
}
}
public void removeAutoPilotGeofence() {
try {
Log.i("", "autopilot remove autopilot geofence");
List<String> list = new ArrayList<String>();
list.add("3");
if(mGeofencingClient == null)
mGeofencingClient = LocationServices.getGeofencingClient(context);
mGeofencingClient.removeGeofences(list).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Utils.appendLog("GEOFENCE removeAutoPilotGeofence Success removing geofences!", "I", Constants.TRACKER);
Log.i("", "GEOFENCE removeAutoPilotGeofence Success removing geofences!");
PSApplicationClass.getInstance().pref.setGeoCenterString(context, "-1");
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Utils.appendLog("GEOFENCE removeAutoPilotGeofence FAILURE removing geofences!" + e.getMessage(), "I", Constants.TRACKER);
Log.i("", "GEOFENCE removeAutoPilotGeofence FAILURE removing geofences!" + e.getMessage());
}
});
Utils.appendLog("GEOFENCE: Disabling geofence done removeAutoPilotGeofence", "E", Constants.TRACKER);
} catch (final Exception e) {
if (e.getMessage().contains("GoogleApiClient") && e.getMessage().contains("not connected")) {
PSLocationService.getInstance(context).startLocationClient();
Handler han = new Handler();
han.postDelayed(new Runnable() {
#Override
public void run() {
Utils.appendLog("autopilot2 error will try again", "E", Constants.TRACKER);
removeAutoPilotGeofence();
}
}, 1000);
}
Log.i("", "autopilot2 error replaceFragment autopilot geofence:" + e.getMessage());
Utils.appendLog("autopilot2 error replaceFragment autopilot geofence:" + e.getMessage(), "E", Constants.TRACKER);
}
}
public void setGeofenceRequest(final Location location) {
ArrayList geofences = new ArrayList<>();
geofences.add(new Geofence.Builder()
.setRequestId("3")
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT)
.setCircularRegion(
location.getLatitude(), location.getLongitude(), PSLocationService.kPSGeofencingDistanceMedium)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build());
//ADDING GEOFENCES
if (geofences.size() > 0) {
if(mGeofencingClient == null)
mGeofencingClient = LocationServices.getGeofencingClient(context);
mGeofencingClient.addGeofences(getGeofencingRequest(location, geofences), getGeofencePendingIntent()).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
RealmLocation realmLocation = new RealmLocation(location.getLatitude(), location.getLongitude(), location.getTime() / 1000, null, true);
realmLocation.setAccuracy(location.getAccuracy());
realmLocation.setSpeed(location.getSpeed());
PSApplicationClass.getInstance().pref.setGeoCenter(realmLocation, context);
Utils.appendLog("GEOFENCE setGeofenceRequest Success adding geofences!" + location.getLatitude() + " / " + location.getLongitude(), "I", Constants.TRACKER);
Log.i("", "GEOFENCE setGeofenceRequest Success adding geofences! " + location.getLatitude() + " / " + location.getLongitude());
PSLocationService.getInstance(context).stopLocationClient();
PSMotionService.getInstance(context).buildGoogleApiClient();
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Utils.appendLog("GEOFENCE setGeofenceRequest FAILURE adding geofences!" + e.getMessage(), "I", Constants.TRACKER);
Log.i("", "GEOFENCE setGeofenceRequest FAILURE adding geofences!" + e.getMessage());
}
});
Log.i("", "geof autopilot2 will set geofence for autopilot-3");
}
}
/**
* Gets a PendingIntent to send with the request to add or remove Geofences. Location Services
* issues the Intent inside this PendingIntent whenever a geofence transition occurs for the
* current list of geofences.
*
* #return A PendingIntent for the IntentService that handles geofence transitions.
*/
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(context, PSGeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
return PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
/**
* Builds and returns a GeofencingRequest. Specifies the list of geofences to be monitored.
* Also specifies how the geofence notifications are initially triggered.
*/
private GeofencingRequest getGeofencingRequest(Location location, ArrayList<Geofence> geofences) {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
// The INITIAL_TRIGGER_ENTER flag indicates that geofencing service should trigger a
// GEOFENCE_TRANSITION_ENTER notification when the geofence is added and if the device
// is already inside that geofence.
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_EXIT);
// Add the geofences to be monitored by geofencing service.
builder.addGeofences(geofences);
// Return a GeofencingRequest.
return builder.build();
}
}
I have in it also the code to remove and add the geofences, and the listener always goes into onSuccess regarding adding them.
For starters, I would not put this code inside a BroadcastReceiver.
Besides being bad practice, the component might be shutdown before the code has finished executing.
Please consider starting a Service from your Receiver, if you need to run code that might take some time.
Otherwise for a short execution time, you may use an IntentService.
By looking at your code, I'm aware of two reasons your Geofences are not working as expected:
1) The nature of Geofences
Geofences API retrieves your location mostly from WiFi / Cellular Data, which is often unavailable.
I tried to use Geofences once, and I found them very inaccurate. I switched to LocationManager making it use pure GPS location and it met my expectations.
Please see this answer, which advises to
Poll the GPS hardware on an interval without doing anything with the result and you'll start getting more accurate geofences.
I have never tried Google's FusedLocation API, but I have heard people saying it worked very well for them.
If you use LocationManager, you will have to implement your 'Geofencing logic' yourself; you can easily do it with Location.distanceTo(Location).
Example:
final float distanceFromCenter = currentLocation.distanceTo(this.destination);
if (distanceFromCenter <= YOUR_RADIUS_IN_METERS) {
// you are inside your geofence
}
2) CPU is not active
The fact that the Geofences are active, does not necessarily mean that your phone is awake and computing location checks.
To fix that, you can start a ForegroundService from your BroacastReceiver. The Service should hold a partial WakeLock as well.
This guarantees that:
The OS does not kill the service (or better: less chance to be killed...)
The user is aware of the service and can dismiss it if necessary
The CPU is running. Therefore you can be sure that the code that retrieves the location is running (please remember to to release the WakeLock when the service stops).
Please note that Android may still kill your service if necessary.
You can find plenty of examples on the web on how to start a ForegroundService from a BroadcastReceiver, how to hold a WakeLock and so on...
Also, check out to the new Android O API, that brought some minor changes to the ForegroundService and other components.
PS: I have developed and application that uses all the components mentioned above (except for the FusedLocation) and I was extremely satisfied.
EDIT: Answering OP's questions
Okey, let's try to make some order here, otherwise future readers may easily get confused. I'll start by answering what written in the original question and the 'bounty banner', then the OP edits, and finally the questions the OP placed in the comments.
1) Original question
Is the triggering event also being dismissed, when the app is cleaned by the garbage collector?
Most probably yes. See this answer where OP implemented a service that runs in a separate process, in order to make geofence be triggered even when the app is killed.
I need to understand what causes the geofences not to get called, if enough time has passed
Plenty of reasons. See my original answer.
I saw an implementation of the geofence logic with an Service instead of a broadcast receiver, will that work better?
A Receiver and a Service are two different things. Please read Android's documentation. You can start a Service from a BroadcastReceiver, which is usually the preferred way to 'receive' PendingIntents and do something with them.
2) Edits
Please note that I did not tell you to replace the BroadcastReceiver with a Service, but that it might be a good idea to start a Service from your Receiver and handle all your logic there.
Making your IntentService a Singleton class is not necessary as (from IntentService documentation)
All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time.
Do not store Context into a Singleton class or some static references. I'm impressed Android Studio did not warn you.
3) Comments
I need this to work 24/7 hence I cannot use the location all the time, cause of obvious battery issues.
Please read Android Oreo Background Execution Limits. This might be an issue for you.
Also now that I changed to a intentService, is that enough to ensure it should stay awake?
No, as I said, you probably need a partial WakeLock in order to turn on the CPU.
Do I need to initiate it another way, in order to keep it in the foreground?
Yes. In order to start a Foreground Service, you need to call startForeground(int, Notification)
Please note: IntentServices lifespan is limited to the end of the onHandleIntent() function. They are not supposed to live for more than a few seconds, typically. Use the Service class if you want to start a Foreground.
Moreover, as said in the original answer, a new Foreground API is available and preferred for Android Oreo.
Not a question, just a notice: I need to use here Geofencing. (Geofencing will start if necessary the gps
Ok perfect. See what works best for you.
I am trying to subscribe to location updates via Google's FusedLocationProviderApi. I want to receive updates in the background, so that I will receive updates even if the app is killed. Following the online documentation as best as I can, I've written the following code. Note: this is being done in an intent service, not on the UI thread, which is why I'm using blocking connect/result methods.
private void startLocationServices(String deviceId, int pollingInterval) {
Log.i(TAG, "Starting location services with interval: " + pollingInterval + "ms");
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
wakeLock.acquire();
final GoogleApiClient googleApiClient =
new GoogleApiClient.Builder(this)
.addApi(LocationServices.API)
.build();
ConnectionResult result = googleApiClient.blockingConnect();
if (!result.isSuccess() || !googleApiClient.isConnected()) {
Log.e(TAG, "Failed to connect to Google Api");
wakeLock.release();
return;
}
LocationRequest locationRequest = new LocationRequest();
locationRequest.setInterval(pollingInterval);
locationRequest.setFastestInterval(10000);
locationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
Intent locationIntent = new Intent(this, GeoBroadcastReceiver.class);
locationIntent.putExtra(EXTRA_LOCATION_UPDATE_DEVICE_ID, deviceId);
locationIntent.setAction(GeoBroadcastReceiver.ACTION_LOCATION_UPDATE);
PendingIntent locationPendingIntent = PendingIntent.getBroadcast(
this, 0, locationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
PendingResult pendingResult = LocationServices.FusedLocationApi
.requestLocationUpdates(googleApiClient, locationRequest, locationPendingIntent);
Result requestResult = pendingResult.await();
Status requestStatus = requestResult.getStatus();
if (requestStatus.isSuccess()) {
Log.i(TAG, "Successfully subscribed to location updates.");
} else {
Log.e(TAG, String.format(
"Failed subscribe to location updates. Error code: %d, Message: %s.",
requestStatus.getStatusCode(),
requestStatus.getStatusMessage()));
}
googleApiClient.disconnect();
wakeLock.release();
}
When I run this, I see that requestStatus.isSuccess() returns true, indicating that I've successfully subscribed to the location updates. Additionally, The GeoBroadcastReciever, which extends WakefulBroadcastReceiver, receives an intent at the correct polling interval, with the correct action. Good so far, it would seem. Here is what I'm doing in the onReceive method for the GeoBroadcastReceiver:
if (LocationResult.hasResult(intent)) {
LocationResult locationResult = LocationResult.extractResult(intent);
Location location = locationResult.getLastLocation();
if (location != null) {
GeoMonitoringService.wakefulLocationUpdate(context, location);
} else {
Log.e(TAG, "LocationResult does not contain a LastLocation.");
}
} else {
Log.e(TAG, "Intent does not contain a LocationResult.");
}
The problem is, whenever the intent comes in, it does not contain the LocationResult, nor does it contain the LocationAvailabilityResult. I inspected the incoming intent in the debugger, and the only item in the intent's extras is the extra I added when setting up the intent (the device id). As such, LocationResult.hasResult() returns false. Every single time.
I've tried this on a Galaxy Note 4 running 4.0.1, and a Nexus 4 running 5.1.1, with the same result.
If I disable location on the phone, I stop receiving intents altogether, as expected.
Remove the extras from the pending intent, otherwise the location result is not delivered. I can't find where in the documentation this is explained but I found out after lot of trial and error.
A workaround (Christophe Beyls suggested that only Intent Data should be used)
So, since I only need to send a few parameters, so I do something like this:
while building the Intent before the requestLocationUpdates:
intent.setData(Uri.parse("http://a.com/a?"+ Param1+ "?" + Param2+ "?" + Param3);
and in the BroadcastReceiver:
String[] parameters = intent.getDataString().split("[?]");
This works fine, and intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED)
does return the location.
You can use:
int id = 7;
String name = "myName";
uriBuilder.scheme("http")
.authority("workaround.com")
.appendPath("extra")
.appendQueryParameter("id", String.valueOf(id))
.appendQueryParameter("name", name);
intent.setData(uriBuilder.build());
and
#Override
protected void onHandleIntent(Intent intent) {
if (LocationResult.hasResult(intent)) {
int id = Integer.valueOf(uri.getQueryParameter("id"));
String name = uri.getQueryParameter("name");
....
}
}
I'm trying to implement a Geofencing mechanism where a geofence is monitored and once the user exits the current geofence, the current co-ordinates are used to create a new geofence and db query is initiated for fetching some data.
My problem is that the pending intent is never fired.
From the logs i can see that the geofences are being added into the location client. However no pending intents are fired upon location change.(i've set the fence radius at 2m and i've walked over 100mts). Is there something wrong in the way i've declared the intent service ?
Here is the intent service class.
public class GeoFenceIntentService extends IntentService{
private static final String mIntentName = "GeoFenceIntentService";
public GeoFenceIntentService() {
super(mIntentName);
}
#Override
protected void onHandleIntent(Intent intent) {
int transitionType = LocationClient.getGeofenceTransition(intent);
Log.e(TAG,"Inside fence handler");
if(transitionType == Geofence.GEOFENCE_TRANSITION_EXIT){
//Query DB here with current co-ords
//create new GeoFence
Location location = LocationHelper.getInstance(mContext).getLastLocation();
mLat = String.valueOf(location.getLatitude());
mLong = String.valueOf(location.getLongitude());
addGeofenceToMonitor(location);
queryDb();
}
}
}
Also here is where i add the pending intents and the geofence to the location client
addGeofenceToMonitor(Location location){
List<Geofence> list = new ArrayList<Geofence>();
list.add(getNewGeofence(location.getLatitude(), location.getLongitude()));
PendingIntent pendingIntent = PendingIntent.getService(mContext, 0,
new Intent(mContext,GeoFenceIntentService.class), PendingIntent.FLAG_UPDATE_CURRENT);
OnRemoveGeofencesResultListener removeListener = new OnRemoveGeofencesResultListener() {
#Override
public void onRemoveGeofencesByRequestIdsResult(int statusCode, String[] requestIDs) {
//To be used
}
#Override
public void onRemoveGeofencesByPendingIntentResult(int statusCode,PendingIntent pendingIntent) {
//Not used
}
};
LocationHelper.getInstance(mContext).removeGeoFence(mGeofenceRequestIDs, removeListener);
OnAddGeofencesResultListener addListener = new OnAddGeofencesResultListener() {
#Override
public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds) {
if(statusCode != LocationStatusCodes.SUCCESS){
//handle error cases
}
else
Log.i(TAG, "Successfully added Geofence "+geofenceRequestIds[0]+" for monitoring");
}
};
LocationHelper.getInstance(mContext).addGeoFence(list, pendingIntent, addListener);
}
Here is the snippet from the manifest file
<service
android:name="com.myexample.sample.GeoFenceIntentService"
android:label="#string/app_name"
android:exported="true">
</service>
Read this.
Have you checked the position estimation circle you are getting? You can use mock locations app to set the position as well as the accuracy circle. Your geofence may be too small to accommodate your position circle and that is why the events are not triggered.
Android GeoFences never enable the GPS (because their API is awful and their device power consumption is already so out of hand). You have to set up your geofences and then constantly poll the GPS separately if you want geofencing over GPS.
The handler of the GPS polling can be null, the poll only exists to force accurate information into their awful location API and in turn trigger the fences.
I have setup an alarm which is received by a BroadcastReceiver which launches a WakefulIntentService (class LocationMonitor). In the LocationMonitor I have :
private static final int MIN_TIME_BETWEEN_SCANS = 1 * 30 * 1000;
private static final int MIN_DISTANCE = 0;
#Override
protected void doWakefulWork(Intent intent) {
final CharSequence action = intent.getAction();
if (action == null) { // monitor command from the alarm manager
// the call below enables the LocationReceiver
BaseReceiver.enable(this, ENABLE, LocationReceiver.class);
if (lm == null) lm = (LocationManager) this
.getSystemService(Context.LOCATION_SERVICE);
Intent i = new Intent(this, LocationReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,
MIN_TIME_BETWEEN_SCANS, MIN_DISTANCE, pi);
} else if (ac_location_data.equals(action)) {
final Bundle extras = intent.getExtras();
if (extras != null) {
final Location loc = (Location) extras
.get(LocationManager.KEY_LOCATION_CHANGED);
if (loc == null) {
w("NULL LOCATION - EXTRAS : " + extras); //Log.w
// while gps is disabled I keep getting this :
// NULL LOCATION - EXTRAS : Bundle[{providerEnabled=false}]
} else {
final double lon = loc.getLongitude();
final double lat = loc.getLatitude();
w("latitude :" + lat + " -- longitude : " + lon);
}
}
}
}
I have several issues with the code above.
If GPS is initially disabled and then I enable it I get a bunch of W/GpsLocationProvider(...): Unneeded remove listener for uid 1000. The warning comes from here. I can't find in the code where is this removing of listeners triggered, nor can I see where they are assigned the uid 1000 (apparently the system server).
When I enable the gps I get the location as expected and then a "RemoteException"
LocationManagerService(...): RemoteException calling onLocationChanged on Receiver{4083ee68 Intent PendingIntent{4084e6b8: PendingIntentRecord{4083ef78 gr.uoa.di.monitoring.android broadcastIntent}}}mUpdateRecords: {gps=UpdateRecord{40838180 mProvider: gps mUid: 10064}}
which is not really a RemoteException, just a PendingIntent.CancelledException - the message is quite misleading. Or so I think : it comes from here which calls this. My question is : why is it reusing the Intent - shouldn't the FLAG_ONE_SHOT dispose of it ?
But the most important question is : when I register a PendingIntent like this what intents do I expect to receive ? And what flags should I use ?
Keep in mind I am using this pattern cause I want to have the phone update its position even when asleep and this achieves it (I do get the location updates). I try to simulate requestSingleUpdate (unavailable in 2.3) using FLAG_ONE_SHOT.
Receiver :
public final class LocationReceiver extends BaseReceiver {
private static final Class<? extends Monitor> MONITOR_CLASS =
LocationMonitor.class;
#Override
public void onReceive(Context context, Intent intent) {
d(intent.toString());
final String action = intent.getAction();
d(action + "");
final Intent i = new Intent(context, MONITOR_CLASS);
i.fillIn(intent, 0); // TODO do I need flags ?
i.setAction(ac_location_data.toString());
WakefulIntentService.sendWakefulWork(context, i);
}
}
To this question:
when I register a PendingIntent like this what intents do I expect to
receive ? And what flags should I use ?
When you register for location updates and pass a PendingIntent, this PendingIntent will be triggered when the LocationManager decides to inform you about a location update. You can provide pretty much whatever you want, depending on what you want to happen when the PendingIntent is triggered. The LocationManager will add an extra to the Intent that is sent. This extra has the bundle key LocationManager.KEY_LOCATION_CHANGED and the object associated with that key is a Location object.
LocationManager will use this PendingIntent again and again to inform your app of location updates, so I think using PendingIntent.FLAG_ONE_SHOT is probably not such a good idea. If you only want a single update, why don't you just unregister after you get one update?
EDIT: Add code to cancel any previously requested updates before registering for updates
Before you call registerLocationUpdates(), do this to cancel any previously registered updates:
Intent i = new Intent(this, LocationReceiver.class);
// Get any existing matching PendingIntent
PendingIntent pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_NO_CREATE);
if (pi != null) {
// Cancel any updates for this PendingIntent, because we are about to
// invalidate it
lm.removeUpdates(pi);
}
// Create a new PendingIntent and cancel any previous one
pi = PendingIntent.getBroadcast(this, NOT_USED, i,
PendingIntent.FLAG_CANCEL_CURRENT);
// Now register for location updates...
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER,
MIN_TIME_BETWEEN_SCANS, MIN_DISTANCE, pi);
NOTE: Actually, I don't know why you need to cancel any previous PendingIntent and create a new one in this case. You can just get a PendingIntent and if you have already registered for location updates with that PendingIntent, I don't think that registering again will cause the PendingIntent to be used multiple times. If you want to try that, all you need to do is to remove PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT from your existing code. I think that is a better/cleaner/clearer solution.
I have searched quite a bit and I'm not totally clueless. I have implemented a temporary solution on my end but was wondering if there is a better approach out there.
I have an app that sends a person's location after every 60 seconds to a server. On my dashboard (the main screen that will go to onPause after application starts), I have registered a LocationManager with the following code:
service = (LocationManager) getSystemService(LOCATION_SERVICE);
boolean enabled = service
.isProviderEnabled(LocationManager.GPS_PROVIDER);
if (!enabled)
{
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(intent);
}
else
{
Criteria criteria = new Criteria();
provider = service.getBestProvider(criteria, false);
service.requestLocationUpdates(provider, 10000, 50, this);
Location location = service.getLastKnownLocation(provider);
// Initialize the location fields
if (location != null)
{
onLocationChanged(location);
}
else
{
Log.d("Location: ", "No update received");
}
}
However, as I mentioned, this activity will be minimized by the user (by pressing the home button). There is a service that gets called every 60 seconds by an AlarmManager. That service accesses static variables from the Dashboard Activity (lat, lon) and sends it to the server.
My question:
If the activity goes onPause, will the requestLocationUpdates function stop? Or will it keep working?
If it keeps working, it will keep updating the two lat and lon static String objects and the service will keep getting updated values. If they stop, the service will keep getting the same old values again and again.
Also, is there a better way to approach this problem? Using a mix of GPS Provider and Network Provider? (I need fairly accurate values).
EDIT
Here's my Alarm. This code is inside Login Activity
Intent i = new Intent(con, LocationPoller.class);
i.putExtra(LocationPoller.EXTRA_INTENT, new Intent(con,
Login.class));
i.putExtra(LocationPoller.EXTRA_PROVIDER,
LocationManager.GPS_PROVIDER);
gps = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(con, 0, i, 0);
gps.setRepeating(AlarmManager.RTC_WAKEUP, c.getTimeInMillis(),
10 * 1000, pi);
Log.d("Service: ",
"GPS Service started and scheduled with AlarmManager");
Here's my receiver (also within Login activity)
private class ReceiveMessages extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
Location loc = (Location) intent.getExtras().get(
LocationPoller.EXTRA_LOCATION);
String msg;
if (loc == null)
{
msg = intent.getStringExtra(LocationPoller.EXTRA_ERROR);
}
else
{
msg = loc.toString();
}
if (msg == null)
{
msg = "Invalid broadcast received!";
}
Log.d("GPS Broadcast: ", msg);
}
}
Nothing's happening :s Not getting anything on logcat which means the broadcast isn't being received.
When activity goes on pause, all registered listeners will stop. Better way to implement this is, alarm manager sent a broadcast every 60 seconds, this broadcast receiver starts a service and this service will request a location on Wakeful thread, once location information is retrieved, update the location on server.
There is an Open source library available with an example (courtesy CommonsWare), please refer below link. Its under Apache 2.0 license
Location Polling Library
Please find my sample project using above library. I have modified few things in the above library and created my own version.
Location Polling Demo Application