Android geofence with mock location provider - android

I have started developing on Android with last Location services feature : Geofences !! Is there any known problem with mock location provider ? Following example here (https://developer.android.com/training/location/geofencing.html) my intent service never fired even if the current location is inside geofence. I'm using FakeGPS android app as mock location provider and if I simulate a route I see the location changes on Google Maps app, so the mock location provider is working well. Any ideas ?
Thanks.
Paolo.

I tried forever to get this to work. What a pain Google! Since it says geofences can easily be tested using mocks.
The magic trick is to use the provider name "network" in the Location passed to setMockLocation.
Location location = new Location("network");
location.setLatitude(latitude);
location.setLongitude(longitude);
location.setTime(new Date().getTime());
location.setAccuracy(3.0f);
location.setElapsedRealtimeNanos(System.nanoTime());
LocationServices.FusedLocationApi.setMockLocation(_googleApiClient, location);

Actually Intent service used in the mentioned example works good if your app is in foreground but when the app is in background, this IntentService is never called.So we need to use Broadcast-Receiver instead of Intent service.
I found this blog helpful in getting solution.
http://davehiren.blogspot.in/2015/01/android-geofence-stop-getting.html

Geofences use FusedLocationProviderApi so to mock them you have to use FusedLocationProviderApi.setMockLocation

Make sure to enable mock locations on your phone. Select Settings->Developer Options->"Allow mock locations".

LocationServices.FusedLocationApi.setMockMode(googleApiClient, true)
needs to be used before setting Mock Location.

You can use broadcast receiver instead of activity like this
public class GeofenceReceiver extends BroadcastReceiver
implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
ResultCallback<Status>{
GoogleApiClient mGoogleApiClient;
PendingIntent mGeofencePendingIntent ;
Context mContext;
#Override
public void onReceive(Context context, Intent intent) {
mContext = context;
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addOnConnectionFailedListener(this)
.addConnectionCallbacks(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
#Override
public void onConnected(#Nullable Bundle bundle) {
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
// The GeofenceRequest object.
getGeofencingRequest(),
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
} catch (SecurityException securityException) {
Log.i(getClass().getSimpleName(),securityException.getMessage());
}
}
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
}
/**
* Runs when the result of calling addGeofences() and removeGeofences() becomes available.
* Either method can complete successfully or with an error.
*
* Since this activity implements the {#link ResultCallback} interface, we are required to
* define this method.
*
* #param status The Status returned through a PendingIntent when addGeofences() or
* removeGeofences() get called.
*/
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(getClass().getSimpleName(),"Success");
} else {
// Get the status code for the error and log it using a user-friendly message.
Log.i(getClass().getSimpleName(),getErrorString(status.getStatusCode()));
}
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofences(getGeofecne());
return builder.build();
}
private List<Geofence> getGeofecne(){
List<Geofence> mGeofenceList = new ArrayList<>();
//add one object
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId("key")
// Set the circular region of this geofence.
.setCircularRegion(
25.768466, //lat
47.567625, //long
50) // radios
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
//1000 millis * 60 sec * 5 min
.setExpirationDuration(1000 * 60 * 5)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(
Geofence.GEOFENCE_TRANSITION_DWELL)
//it's must to set time in millis with dwell transition
.setLoiteringDelay(3000)
// Create the geofence.
.build());
return mGeofenceList;
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
return PendingIntent.getService(mContext, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
}
}
check out my repo, there is a full example of using geofence
https://github.com/3zcs/Geofence

Related

Show Users who are in 10 KM radius from User current location Android

I have a requirement where I want to get the user's current location and based on the user's current location I want to get other user's information from app server and want to show them in a list. Users should be within 5 or 10 km radius.
I fetched users data from server showing it in the app but I want to show within that particular radius. Any help is appreciable.
You need to use Geofences, Please follow the below steps you definitely get the result as you want.
Let's see how it's work.
Geofencing combines awareness of the user's current location with awareness of the user's proximity to locations that may be of interest. To mark a location of interest, you specify its latitude and longitude. To adjust the proximity for the location, you add a radius. The latitude, longitude, and radius define a geofence, creating a circular area, or fence, around the location of interest.
You can have multiple active geofences, with a limit of 100 per device user.
Now, let's see how we use it in our application,
Set up for Geofence Monitoring
The first step in requesting geofence monitoring is to request the necessary permission. To use geofencing, your app must request ACCESS_FINE_LOCATION. To request this permission, add the following element as a child element of the <manifest> element in your app manifest:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
If you want to use an IntentService to listen for geofence transitions, add an element specifying the service name. This element must be a child of the <application> element:
<application
android:allowBackup="true">
...
<service android:name=".GeofenceTransitionsIntentService"/>
<application/>
To access the location APIs, you need to create an instance of the Geofencing client. To learn how to connect your client:
private GeofencingClient mGeofencingClient;
// ...
mGeofencingClient = LocationServices.getGeofencingClient(this);
Create and Add Geofences
Note: On single-user devices, there is a limit of 100 geofences per
app. For multi-user devices, the limit is 100 geofences per app per
device user.
Create geofence objects
First, use Geofence.Builder to create a geofence, setting the desired radius, duration, and transition types for the geofence. For example, to populate a list object named mGeofenceList:
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId(entry.getKey())
.setCircularRegion(
entry.getValue().latitude,
entry.getValue().longitude,
Constants.GEOFENCE_RADIUS_IN_METERS
)
.setExpirationDuration(Constants.GEOFENCE_EXPIRATION_IN_MILLISECONDS)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER |
Geofence.GEOFENCE_TRANSITION_EXIT)
.build());
Specify geofences and initial triggers
The following snippet uses the GeofencingRequest class and its nested GeofencingRequestBuilder class to specify the geofences to monitor and to set how related geofence events are triggered:
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}
Define an Intent for geofence transitions
The Intent sent from Location Services can trigger various actions in your app, but you should not have it start an activity or fragment, because components should only become visible in response to a user action. In many cases, an IntentService is a good way to handle the intent. An IntentService can post a notification, do long-running background work, send intents to other services, or send a broadcast intent. The following snippet shows how to define a PendingIntent that starts an IntentService:
public class MainActivity extends AppCompatActivity {
// ...
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
Add geofences
To add geofences, use the GeofencingClient.addGeofences() method. Provide the GeofencingRequest object, and the PendingIntent. The following snippet demonstrates processing the results:
mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Geofences added
// ...
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// Failed to add geofences
// ...
}
});
Handle Geofence Transitions
Note: On Android 8.0 (API level 26) and higher, if an app is running in the background while monitoring a geofence, then the device
responds to geofencing events every couple of minutes. To learn how to
adapt your app to these response limits, see Background Location
Limits.
public class GeofenceTransitionsIntentService extends IntentService {
// ...
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.getErrorCode());
Log.e(TAG, errorMessage);
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Get the geofences that were triggered. A single event can trigger
// multiple geofences.
List<Geofence> triggeringGeofences = geofencingEvent.getTriggeringGeofences();
// Get the transition details as a String.
String geofenceTransitionDetails = getGeofenceTransitionDetails(
this,
geofenceTransition,
triggeringGeofences
);
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
geofenceTransition));
}
}
Stop Geofence Monitoring
mGeofencingClient.removeGeofences(getGeofencePendingIntent())
.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
// Geofences removed
// ...
}
})
.addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
// Failed to remove geofences
// ...
}
});
you should implement logic on server based on the latitude and longitude and return the data

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.

sending extra to requestLocationUpdates intentService breaks location updates

I'm having trouble sending a string extra with my PendingIntent that I pass to LocationServices.FusedLocationApi.requestLocationUpdates(GoogleApiClient client, LocationRequest request, PendingIntent callbackIntent).
It appears that the username extra i'm putting onto the Intent is mangling the location that requestLocationUpdates is trying to hand off to my IntentService as intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED) returns null.
EDIT
I've tried making a User class that implements Parcelable and putting it as an extra:
mRequestLocationUpdatesIntent.putExtra("username", new User(username));
and I've also tried to put the Parcelable User inside a Bundle as suggested via comment in this bug report https://code.google.com/p/android/issues/detail?id=81812:
Bundle userBundle = new Bundle();
userBundle.putParcelable("user", new User(username));
mRequestLocationUpdatesIntent.putExtra("user", userBundle);
in my service:
Bundle userBundle = intent.getBundleExtra("user");
User user = userBundle.getParcelable("user");
String username = user.getUsername();
However neither of these approaches has made any difference. Whenever I put any extra onto my intent, the location is never added to the intent when the updates occur.
I setup this IntentService to handle location updates:
public class LocationUpdateService extends IntentService {
private final String TAG = "LocationUpdateService";
public LocationUpdateService() {
super("LocationUpdateService");
}
#Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "onHandleIntent");
Bundle extras = intent.getExtras();
Log.d(TAG, "keys found inside intent: " + TextUtils.join(", ", extras.keySet()));
String username = intent.getStringExtra("username");
if (username != null) {
Log.d(TAG, "username: " + username);
} else {
Log.d(TAG, "username: null");
}
if (!intent.hasExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED)) {
Log.d(TAG, "intent does not have location :(");
}
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
if (location == null) {
Log.d(TAG, "location == null :(");
}
Log.d(TAG, "latitude " + String.valueOf(location.getLatitude()));
Log.d(TAG, "longitude " + String.valueOf(location.getLongitude()));
...
}
}
When the user clicks a button, the startLocationUpdates is called in my main activity:
main activity class:
...
Boolean mLocationUpdatesEnabled = false;
protected void createLocationRequest() {
mLocationRequest = new LocationRequest();
mLocationRequest.setInterval(LOCATION_UPDATE_INTERVAL);
mLocationRequest.setFastestInterval(LOCATION_UPDATE_FASTEST_INTERVAL);
mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
}
protected void startLocationUpdates() {
Log.d(TAG, "startng location updates...");
mLocationUpdatesEnabled = true;
if (mLocationRequest == null) {
createLocationRequest();
}
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
// create a PendingIntent
mRequestLocationUpdatesPendingIntent = PendingIntent.getService(getApplicationContext(), 0,
mRequestLocationUpdatesIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
// request location updates
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
mLocationRequest,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates started");
}
protected void stopLocationUpdates() {
Log.d(TAG, "stopping location updates...");
mLocationUpdatesEnabled = false;
LocationServices.FusedLocationApi.removeLocationUpdates(
mGoogleApiClient,
mRequestLocationUpdatesPendingIntent);
Log.d(TAG, "location updates stopped");
}
This all works well and good; When the user presses the button, toggleLocationUpdates is called, which calls LocationServices.FusedLocationApi.requestLocationUpdates which calls my LocationUpdateService where I'm able to get the location.
The trouble comes when I tried to put a string extra onto my Intent using Intent.putExtra(String, String):
main activity class:
...
protected void startLocationUpdates(String username) {
....
// create the Intent to use WebViewActivity to handle results
Intent mRequestLocationUpdatesIntent = new Intent(this, LocationUpdateService.class);
//////////////////////////////////////////////////////////////////
//
// When I put this extra, IntentService sees my username extra
// but the parcelableExtra `location` == null :(
//
//////////////////////////////////////////////////////////////////
mRequestLocationUpdatesIntent.putExtra("username", username);
...
}
...
EDIT I had started the next sentence as a statement rather than a question: "I am using..."
Am I using the correct approach to sending some extra data to this location update handling IntentService or is there a more-sane way to go about this?
Is this a bug or just poor documentation?
Using the IntentService coupled with the FusedLocationProviderAPI will present issues. From the Developer Docs titled Receiving Location Updates:
Depending on the form of the request, the fused location provider
either invokes the LocationListener.onLocationChanged() callback
method and passes it a Location object, or issues a PendingIntent that
contains the location in its extended data. The accuracy and frequency
of the updates are affected by the location permissions you've
requested and the options you set in the location request object
Further, a PendingIntent is used for extending permissions for another piece of code (FusedLocationProviderAPI in Google Play Services) to execute their code within your apk. An IntentService is used to start a Service defined within the scope of your apk.
So, the method requires an implementation of LocationListener for foreground updates, or a PendingIntent for background updates coupled with a Broadcast Receiver.
This is a working example of some methods used to request location updates from a PendingIntent coupled with extra values.
Note: LocalStorage.java is a utility class for storing local variables, it is not part of the Android API
GPSPlotter
/**
* Private helper method to initialize the Google Api Client with the
* LocationServices Api and Build it for use.
*/
private void initializeGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
/**
* Private helper method to determine whether or not GooglePlayServices
* are installed on the local system.
*
* #return services are installed.
*/
private boolean googlePlayServicesInstalled() {
int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(mContext);
return result == ConnectionResult.SUCCESS;
}
/**
* Private method to build the Api Client for use with the LocationServices API.
*/
private synchronized void buildApiClient() {
Log.w(TAG, "Building Google Api Client...");
initializeGoogleApiClient();
}
/**
* Private method used to connect the ApiClient to the Api hosted by Google for
* Accessing Locations.
*/
private void connectClient() {
mGoogleApiClient.connect();
}
/**
* User passes in a requested interval polling time in seconds as an
* integer.
*
* #param theAccount is a reference to the parent activity used for updating views.
*/
public void beginManagedLocationRequests(MyAccount theAccount) {
if (mAccount == null)
mAccount = theAccount;
startBackgroundUpdates();
}
/**
* Public method to end the managed Location Requests.
*/
public void endManagedLocationRequests() {
endBackgroundUpdates();
}
/**
* This method handles the switch in polling rates by stopping and then starting once more the
* background udpates, which in turn sets the interval in another method in the call stack.
* #param theInterval the desired interval polling rate
*/
public void changeRequestIntervals(int theInterval) {
mIntentInterval = theInterval;
if (LocalStorage.getRequestingBackgroundStatus(mContext)) {
endBackgroundUpdates();
startBackgroundUpdates();
}
}
/**
* Private helper method to build an Intent that will be couple with a pending intent uses
* for issuing background Location requests.
*
* #return theIntent
*/
private Intent buildBackgroundRequestIntent() {
Intent intent = new Intent(mContext, BackgroundLocationReceiver.class);
intent.setAction(BACKGROUND_ACTION);
intent.putExtra(User.USER_ID, mUserID);
return intent;
}
/**
* Private helper method used to generate a PendingIntent for use when the User requests background service
* within the FusedLocationApi until the Interval is changed.
*
* #return pendingIntent
*/
private PendingIntent buildRequestPendingIntent(Intent theIntent) {
Log.w(TAG, "building pending intent");
return PendingIntent.getBroadcast(mContext, 0, theIntent, 0);
}
/**
* Private method to start the Location Updates using the FusedLocation API in the background.
*/
private void startBackgroundUpdates() {
Log.w(TAG, "Starting background updates");
if (googlePlayServicesInstalled()) {
LocalStorage.putBackgroundRequestStatus(true, mContext);
LocalStorage.putLocationRequestStatus(true, mContext);
registerAlarmManager();
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, buildLocationRequest(), buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
}
/**
* Private method to end background updates.
*/
private void endBackgroundUpdates() {
Log.w(TAG, "Ending background updates");
LocalStorage.putBackgroundRequestStatus(false, mContext);
LocalStorage.putLocationRequestStatus(false, mContext);
unregisterAlarmManager();
LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, buildRequestPendingIntent(buildBackgroundRequestIntent()));
}
BackgroundLocationReceiver
public class BackgroundLocationReceiver extends BroadcastReceiver {
private static final String TAG = "BLocRec: ";
private static final String UPLOAD_ERROR_MESSAGE = "Background Service to Upload Coordinates Failed.";
private static final String UPLOAD_MESSAGE = "Coordinate Batch Pushed to Database.";
public BackgroundLocationReceiver() {
//Default, no-arg constructor
}
/**
* This method handles any location updates received when the app is no longer in focus. Coordinates are
* stored in the local database and uploaded once every hour.
* #param context the application context
* #param intent is the pending intent
*/
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().matches(GPSPlotter.BACKGROUND_ACTION)) {
Log.w(TAG, "BLR Received-background");
Location location = intent.getParcelableExtra(FusedLocationProviderApi.KEY_LOCATION_CHANGED);
storeLocation(location, context, intent.getStringExtra(User.USER_ID));
}
EDIT
The method below builds a LocationRequest necessary for invoking the requestLocationUpdates() method
/**
* Private helper method used to generate a LocationRequest which will be used to handle all location updates
* within the FusedLocationApi until the Interval is changed.
*
* #return locationRequest
*/
private LocationRequest buildLocationRequest() {
int dateConversion = 1000;
LocationRequest locationRequest = LocationRequest.create();
locationRequest.setInterval(mIntentInterval * dateConversion);
locationRequest.setFastestInterval((mIntentInterval / 2) * dateConversion);
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
Log.w(TAG, "Building location request");
return locationRequest;
}
EDIT
After a long discussion in chat with Catherine, we came to the conclusion that google play services library 7.5 has a bug that does not process the Parcelable Extra Location passed from FusedLocationProviderAPI when other extras are put into the Intent. However, 7.0 does provide this capability. She said that she will submit a bug and we'll see how long it takes the Android team to resolve

Remove Geofence after triggered

I use Geofences in my app, everything works fine except the removal of triggered geofences.
I red the guide from the official documentation of Android but they don't explain how to remove a geofence inside of the IntentService.
Here is the code of the event handler of the service:
#Override
protected void onHandleIntent(Intent intent)
{
Log.e("GeofenceIntentService", "Location handled");
if (LocationClient.hasError(intent))
{
int errorCode = LocationClient.getErrorCode(intent);
Log.e("GeofenceIntentService", "Location Services error: " + Integer.toString(errorCode));
}
else
{
int transitionType = LocationClient.getGeofenceTransition(intent);
if (transitionType == Geofence.GEOFENCE_TRANSITION_ENTER)
{
List <Geofence> triggerList = LocationClient.getTriggeringGeofences(intent);
String[] triggerIds = new String[triggerList.size()];
for (int i = 0; i < triggerIds.length; i++)
{
// Store the Id of each geofence
triggerIds[i] = triggerList.get(i).getRequestId();
Picture p = PicturesManager.getById(triggerIds[i], getApplicationContext());
/* ... do a lot of work here ... */
}
}
else
Log.e("ReceiveTransitionsIntentService", "Geofence transition error: " + Integer.toString(transitionType));
}
}
How can I delete the geofence after he got triggered ?
You can do something like this:
LocationServices.GeofencingApi.removeGeofences(
mGoogleApiClient,
// This is the same pending intent that was used in addGeofences().
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
And your getGeofencePendingIntent() method can look like this:
/**
* 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() {
Log.d(TAG, "getGeofencePendingIntent()");
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(mContext, GeofenceIntentServiceStub.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getService(mContext, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}
You would proceed as you did when adding Geofences (create a LocationClient and wait for it to connect). Once it is connected, in the onConnected callback method, you would call removeGeofences on the LocationClient instance instead and pass it a list of request IDs you want to remove and an instance of OnRemoveGeofencesResultListener as a callback handler.
Of course, you must use the same request IDs you used when creating the GeoFence with GeoFence.Builder's setRequestId.
#Override
public void onConnected(Bundle arg0) {
locationClient.removeGeofences(requestIDsList,
new OnRemoveGeofencesResultListener() {
...
});

Pending Intents not fired

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.

Categories

Resources