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() {
...
});
Related
I am using an intent to switch to Google Maps to get the route to a marker that's shown on a map that's implemented in my app. My question is if there is any way to let my app in background to send the location at certain intervals of time, so when the user gets close to the location, when the app is reopened to have the location updated directly, so I would be able to implement the next step of the app.( the app should do something when he is close)
You can try create and monitor geofence for detect user gets close to the location:
public class MainActivity extends AppCompatActivity {
// ...
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (geofencePendingIntent != null) {
return geofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceBroadcastReceiver.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
geofencePendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
return geofencePendingIntent;
}
and then, when user gets close to the location start tracking it directly from your app. Something like that:
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
// ...
protected void onReceive(Context context, Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = GeofenceStatusCodes.getErrorString(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) {
// 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
);
// Start tracking user position directly here
...
} else if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
// Stop tracking user position directly here
...
else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type,
geofenceTransition));
}
}
}
I'm not sure it will work or not but you can try this one. my guess should work.
ActivityManager activityManager = (ActivityManager) getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.moveTaskToFront(getTaskId(), 0);
Please note that you cannot keep to get the user location in the background when the system is greater than or equal to android O
Background Execution Limits
Solution : You can register a Service in the foreground.
Step 1:Create your own service
Step 2:register with Androidmanifest
Step 3:Create BroadcastReceiver receiver data
Step 4:In onResume bind and onstop unbind service
foreground example from google
I am working on a Geofencing application. The JobIntentService subclass that handles the GeofenceTransitions never receives the intent. I am receiving location updates at one minute interval then creating a new geofence list then adding the geofences based on a user's current location.
Here's my AndroidManifest.xml
........
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.WAKE_LOCK" />
<service
android:name=".GeofenceTransitionsService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
<receiver
android:name=".GeofenceBroadcastReceiver"
android:enabled="true"
android:exported="true" />
.............
My GeofenceBroadcastReceiver.java
public class GeofenceBroadcastReceiver extends BroadcastReceiver {
/**
* Receives incoming intents.
*
* #param context the application context.
* #param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
#Override
public void onReceive(Context context, Intent intent) {
// Enqueues a JobIntentService passing the context and intent as parameters
GeofenceTransitionsService.enqueueWork(context, intent);
}
}
My GeofenceTransitionsService.java that handles the triggered geofences
public class GeofenceTransitionsService extends JobIntentService {
..........
/**
* Convenience method for enqueuing work in to this service
* Enqueue new work to be dispatched to onHandleWork
*/
public static void enqueueWork(Context context, Intent intent) {
Log.d(TAG, "Received intent: " + intent);
enqueueWork(context, GeofenceTransitionsService.class, JOB_ID, intent);
}
#Override
protected void onHandleWork(Intent intent){
// We have received work to do. The system or framework is already
// holding a wake lock for us at this point, so we can just go.
Log.d(TAG, "Received intent: " + intent);
}
}
Here's part of my code in PointOfInterestMapFragment.java that creates a geofencing request, creates the pending intent and adds the geofences
/* Use 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();
//tell Location services that GEOFENCE_TRANSITION_DWELL should be triggered if the
//device is already inside the geofence
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofences(mGeofenceList);
return builder.build();
}//end method getGeofencingRequest
/*Pending intent that starts the IntentService*/
private PendingIntent getGeofencePendingIntent(){
Log.d(TAG, "getPendingIntent()");
//Reuse the pending intent if we already have it
if(mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(getActivity(), GeofenceBroadcastReceiver.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when
// calling addGeofences() and removeGeofences().
mGeofencePendingIntent = PendingIntent.getBroadcast(getActivity()
, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
return mGeofencePendingIntent;
}//end method PendingIntent
/*Add geofences*/
#SuppressWarnings("MissingPermission")
private void addGeofence(){
if(checkPermissions()){
mGeofencingClient.addGeofences(getGeofencingRequest(), getGeofencePendingIntent())
.addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
Log.d(TAG, "Geofence added");
}
})
.addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.d(TAG, "Failed to add geofence: " + e.getMessage());
}
})
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
//drawGeofence();
}
});
}else{
requestPermissions();
}
}//end method addGeofence
Here's the part of code in PointOfInterestMapFragment.java where I am receiving the location updates, populating the GeofenceList then adding geofences
/**
* Creates a callback for receiving location events.
*/
private void createLocationCallback() {
mLocationCallback = new LocationCallback() {
#Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
mCurrentLocation = locationResult.getLastLocation();
mLastUpdateTime = DateFormat.getTimeInstance().format(new Date());
//populateGeofenceList to reflect the new current location bounds
populateGeofenceList();
addGeofence();
}
};
}
When the app executes, the I get the message in log cat from the line of code Log.d(TAG, "getPendingIntent()"); in getGeofencePendingIntent() but never get the message supposed to be displayed in onHandleWork() method
I had a similar 'problem'. The code is fine. In my case, I thought the code was not working because I had not understood properly how a geofence works. I thought to add the geofences, in your case a call to addGeofence() is the trigger, so I was waiting to see the notifications at that particular point in time. However a call to the method only adds the geofences for monitoring, then an intent is only delivered to the service when any of the filters are satisified (Geofence.GEOFENCE_TRANSITION_DWELL, Geofence.GEOFENCE_TRANSITION_EXIT OR Geofence.GEOFENCE_TRANSITION_ENTER) on an added geofence. You can read more from the documentation here
So, you might receive a Geofence added message in your log cat, but that's what it literally means, the geofences have been added not triggered. Wait for some time after the geofence have been added and if any of the filters are satisfied for a geofence that was added, then the intent is sent. So the solution that worked for me was to wait and I received the intent and notifications after some period of time.
If waiting does not work, you might want to extend the GEOFENCE_RADIUS, say to 3000 metres the check to see whether there is any change. Also, set the expiration duration to a higher value or to Geofence.NEVER_EXPIRE
I'm using the relatively new version of the Geofence API in Google's Play Services. The issue (apart from outdated documentation) being that the received Intents (after a geofence exit) don't seem to contain any data.
I add a category myself and this category is successfully retrieved.
I added a serializeable extra too which is perfectly retrieved from the intent.
The above two arguments prove that the intent I am receiving is in fact the one I scheduled for a geofence transition.
What doesn't work.
The GeofenceEvent seems to return defaults for all methods. So I get;
false for hasError()
and -1 for getErrorCode()
getGeofenceTransition() returns -1
the list returned by getTriggeredGeofences() is empty
This seems to contradict the API documentation...
getGeofenceTransition();
-1 if the intent specified in fromIntent(Intent) is not generated for a transition alert; Otherwise returns the GEOFENCE_TRANSITION_ flags value defined in Geofence.
with getErrorCode();
the error code specified in GeofenceStatusCodes or -1 if hasError() returns false.
and hasError();
true if an error triggered the intent specified in fromIntent(Intent), otherwise false
How I add the Geofence
// create fence
Geofence fence = new Geofence.Builder()
.setRequestId(an_id)
.setCircularRegion(lat, long, radius)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_EXIT)
.setExpirationDuration(240 * 60 * 1000)
.build();
// create pendingintent
Intent launch = new Intent(this, same_class.class);
launch.putExtra(extra_object_key, extra_object);
launch.addCategory(category_name);
PendingIntent intent = PendingIntent.getService(
getApplicationContext(),
0, launch,
PendingIntent.FLAG_UPDATE_CURRENT
);
// create add fence request
GeofencingRequest request = new GeofencingRequest.Builder()
.addGeofence(fence)
.build();
// add fence request
PendingResult<Status> result = LocationServices.GeofencingApi.addGeofences(googleApiClient,
request, intent);
// we'll do this synchronous
Status status = result.await();
if (status.getStatusCode() != GeofenceStatusCodes.SUCCESS)
{
Log.e(SERVICE_NAME, "Failed to add a geofence; " + status.getStatusMessage());
return false;
}
How I receive the intent
protected void onHandleIntent(Intent intent)
{
if (intent.hasCategory(category_name))
{
GeofencingEvent event = GeofencingEvent.fromIntent(intent);
if (event.hasError())
{
Log.e(SERVICE_NAME, "Failed geofence intent, code: " + event.getErrorCode());
}
else
{
// checked 'event' here with debugger and prints for the stuff I wrote above
switch (event.getGeofenceTransition())
{
case Geofence.GEOFENCE_TRANSITION_EXIT:
// NEVER REACHED
type extra_object = (type)intent.getSerializableExtra(extra_object_key);
onGeofenceExit(extra_object);
break;
default:
// ALWAYS END UP HERE
throw new AssertionError("Geofence events we didn't register for.");
}
}
}
else
{
throw new AssertionError("Received unexpected intent.");
}
}
Connecting to play services
public void onCreate()
{
super.onCreate();
googleApiAvailable = false;
GoogleApiClient.Builder gApiBuilder = new GoogleApiClient.Builder(getApplicationContext());
gApiBuilder.addApi(LocationServices.API);
gApiBuilder.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks()
{
#Override
public void onConnected(Bundle bundle)
{
googleApiAvailable = true;
}
#Override
public void onConnectionSuspended(int i)
{
googleApiAvailable = false;
}
});
gApiBuilder.addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener()
{
#Override
public void onConnectionFailed(ConnectionResult connectionResult)
{
googleApiAvailable = false;
}
});
googleApiClient = gApiBuilder.build();
}
Where do you connect to googleApiClient? There should be
googleApiClient.connect();
and wait for onConnected or
googleApiClient.blockingConnect();
in you code but I can't see it. Post whole code.
I'm doing a serious research on this topic for many days... I saw many topics here too...
But unfortunately I couldn't find a solution....
I'm writing an app that uses the new Google API for Geofence...
Well, I can handle "ins" and "outs" of a geofence, but only if my app is open! Even if I'm with wifi on, gps on, and 3G on, but the app, it does not trigger any event...Just if the app is open...
I'm using exactly the same GeofenceRequester class of the documentation http://developer.android.com/training/location/geofencing.html .
Even the class been the same I'll post the code here:
package br.com.marrs.imhere.geofence;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import br.com.marrs.imhere.services.ReceiveTransitionsIntentService;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import com.google.android.gms.location.LocationClient.OnAddGeofencesResultListener;
import com.google.android.gms.location.LocationStatusCodes;
/**
* Class for connecting to Location Services and requesting geofences.
* <b>
* Note: Clients must ensure that Google Play services is available before requesting geofences.
* </b> Use GooglePlayServicesUtil.isGooglePlayServicesAvailable() to check.
*
*
* To use a GeofenceRequester, instantiate it and call AddGeofence(). Everything else is done
* automatically.
*
*/
public class GeofenceRequester
implements
OnAddGeofencesResultListener,
ConnectionCallbacks,
OnConnectionFailedListener {
// Storage for a reference to the calling client
private final Activity mActivity;
// Stores the PendingIntent used to send geofence transitions back to the app
private PendingIntent mGeofencePendingIntent;
// Stores the current list of geofences
private ArrayList<Geofence> mCurrentGeofences;
// Stores the current instantiation of the location client
private LocationClient mLocationClient;
/*
* Flag that indicates whether an add or remove request is underway. Check this
* flag before attempting to start a new request.
*/
private boolean mInProgress;
public GeofenceRequester(Activity activityContext) {
// Save the context
mActivity = activityContext;
// Initialize the globals to null
mGeofencePendingIntent = null;
mLocationClient = null;
mInProgress = false;
}
/**
* Set the "in progress" flag from a caller. This allows callers to re-set a
* request that failed but was later fixed.
*
* #param flag Turn the in progress flag on or off.
*/
public void setInProgressFlag(boolean flag) {
// Set the "In Progress" flag.
mInProgress = flag;
}
/**
* Get the current in progress status.
*
* #return The current value of the in progress flag.
*/
public boolean getInProgressFlag() {
return mInProgress;
}
/**
* Returns the current PendingIntent to the caller.
*
* #return The PendingIntent used to create the current set of geofences
*/
public PendingIntent getRequestPendingIntent() {
return createRequestPendingIntent();
}
/**
* Start adding geofences. Save the geofences, then start adding them by requesting a
* connection
*
* #param geofences A List of one or more geofences to add
*/
public void addGeofences(List<Geofence> geofences) throws UnsupportedOperationException {
/*
* Save the geofences so that they can be sent to Location Services once the
* connection is available.
*/
mCurrentGeofences = (ArrayList<Geofence>) geofences;
// If a request is not already in progress
if (!mInProgress) {
// Toggle the flag and continue
mInProgress = true;
// Request a connection to Location Services
requestConnection();
// If a request is in progress
} else {
// Throw an exception and stop the request
throw new UnsupportedOperationException();
}
}
/**
* Request a connection to Location Services. This call returns immediately,
* but the request is not complete until onConnected() or onConnectionFailure() is called.
*/
private void requestConnection()
{
getLocationClient().connect();
}
/**
* Get the current location client, or create a new one if necessary.
*
* #return A LocationClient object
*/
private GooglePlayServicesClient getLocationClient()
{
if (mLocationClient == null) {
mLocationClient = new LocationClient(mActivity, this, this);
}
return mLocationClient;
}
/**
* Once the connection is available, send a request to add the Geofences
*/
private void continueAddGeofences() {
// Get a PendingIntent that Location Services issues when a geofence transition occurs
mGeofencePendingIntent = createRequestPendingIntent();
// Send a request to add the current geofences
mLocationClient.addGeofences(mCurrentGeofences, mGeofencePendingIntent, this);
}
/*
* Handle the result of adding the geofences
*/
#Override
public void onAddGeofencesResult(int statusCode, String[] geofenceRequestIds)
{
// Create a broadcast Intent that notifies other components of success or failure
Intent broadcastIntent = new Intent();
// Temp storage for messages
String msg;
// If adding the geocodes was successful
if (LocationStatusCodes.SUCCESS == statusCode)
{
// Create a message containing all the geofence IDs added.
msg = geofenceRequestIds.toString();
// In debug mode, log the result
Log.d("DEBUG", msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCES_ADDED")
.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", msg);
// If adding the geofences failed
}
else
{
/*
* Create a message containing the error code and the list
* of geofence IDs you tried to add
*/
msg = "Erro adicionando geofence";
// Log an error
Log.e("DEBUG", msg);
// Create an Intent to broadcast to the app
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCE_ERROR")
.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", msg);
}
// Broadcast whichever result occurred
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(broadcastIntent);
// Disconnect the location client
requestDisconnection();
}
/**
* Get a location client and disconnect from Location Services
*/
private void requestDisconnection() {
// A request is no longer in progress
mInProgress = false;
getLocationClient().disconnect();
}
/*
* Called by Location Services once the location client is connected.
*
* Continue by adding the requested geofences.
*/
#Override
public void onConnected(Bundle arg0) {
// If debugging, log the connection
Log.d("DEBUG", "GeofenceRequester connected");
// Continue adding the geofences
continueAddGeofences();
}
/*
* Called by Location Services once the location client is disconnected.
*/
#Override
public void onDisconnected() {
// Turn off the request flag
mInProgress = false;
// In debug mode, log the disconnection
Log.d("DEBUG", "GeofenceRequester disconnected");
// Destroy the current location client
mLocationClient = null;
}
/**
* Get a PendingIntent to send with the request to add 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 createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent(mActivity, ReceiveTransitionsIntentService.class);
/*
* Return a PendingIntent to start the IntentService.
* Always create a PendingIntent sent to Location Services
* with FLAG_UPDATE_CURRENT, so that sending the PendingIntent
* again updates the original. Otherwise, Location Services
* can't match the PendingIntent to requests made with it.
*/
return PendingIntent.getService(
mActivity,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
/*
* Implementation of OnConnectionFailedListener.onConnectionFailed
* If a connection or disconnection request fails, report the error
* connectionResult is passed in from Location Services
*/
#Override
public void onConnectionFailed(ConnectionResult connectionResult) {
// Turn off the request flag
mInProgress = false;
/*
* Google Play services can resolve some errors it detects.
* If the error has a resolution, try sending an Intent to
* start a Google Play services activity that can resolve
* error.
*/
if (connectionResult.hasResolution()) {
try {
// Start an Activity that tries to resolve the error
connectionResult.startResolutionForResult(mActivity, 9000);
/*
* Thrown if Google Play services canceled the original
* PendingIntent
*/
} catch (SendIntentException e) {
// Log the error
e.printStackTrace();
}
/*
* If no resolution is available, put the error code in
* an error Intent and broadcast it back to the main Activity.
* The Activity then displays an error dialog.
* is out of date.
*/
} else {
Intent errorBroadcastIntent = new Intent("br.com.marrs.imhere.ACTION_CONNECTION_ERROR");
errorBroadcastIntent.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES")
.putExtra("br.com.marrs.imhere.EXTRA_CONNECTION_ERROR_CODE",
connectionResult.getErrorCode());
LocalBroadcastManager.getInstance(mActivity).sendBroadcast(errorBroadcastIntent);
}
}
}
And the service:
package br.com.marrs.imhere.services;
import br.com.marrs.imhere.ImHereActivity;
import br.com.marrs.imhere.R;
import com.google.android.gms.location.Geofence;
import com.google.android.gms.location.LocationClient;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import java.util.List;
/**
* This class receives geofence transition events from Location Services, in the
* form of an Intent containing the transition type and geofence id(s) that triggered
* the event.
*/
public class ReceiveTransitionsIntentService extends IntentService {
/**
* Sets an identifier for this class' background thread
*/
public ReceiveTransitionsIntentService()
{
super("ReceiveTransitionsIntentService");
}
/**
* Handles incoming intents
* #param intent The Intent sent by Location Services. This Intent is provided
* to Location Services (inside a PendingIntent) when you call addGeofences()
*/
#Override
protected void onHandleIntent(Intent intent) {
// Create a local broadcast Intent
Intent broadcastIntent = new Intent();
// Give it the category for all intents sent by the Intent Service
broadcastIntent.addCategory("br.com.marrs.imhere.CATEGORY_LOCATION_SERVICES");
// First check for errors
if (LocationClient.hasError(intent)) {
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Log the error
Log.e("DEBUG", "Erro no service LocationClient has error");
// Set the action and error message for the broadcast intent
broadcastIntent.setAction("br.com.marrs.imhere.ACTION_GEOFENCES_ERROR").putExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS", "problemas");
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
// If there's no error, get the transition type and create a notification
} else {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if (
(transition == Geofence.GEOFENCE_TRANSITION_ENTER)
||
(transition == Geofence.GEOFENCE_TRANSITION_EXIT)
) {
// Post a notification
List<Geofence> geofences = LocationClient.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
for (int index = 0; index < geofences.size() ; index++) {
geofenceIds[index] = geofences.get(index).getRequestId();
}
String ids = TextUtils.join(",",geofenceIds);
String transitionType = getTransitionString(transition);
sendNotification(transitionType, ids);
// Log the transition type and a message
Log.d("DEBUG","Ae...n sei pq isso....mas parece que tah ok");
// An invalid transition was reported
} else {
// Always log as an error
Log.e("DEBUG","Erro, erro, erro");
}
}
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the main Activity.
* #param transitionType The type of transition that occurred.
*
*/
private void sendNotification(String transitionType, String ids)
{
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent =
new Intent(getApplicationContext(),ImHereActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(ImHereActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Set the notification contents
builder.setSmallIcon(R.drawable.abs__ic_clear)
.setContentTitle(ids)
.setContentText(transitionType)
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
/**
* Maps geofence transition types to their human-readable equivalents.
* #param transitionType A transition type constant defined in Geofence
* #return A String indicating the type of transition
*/
private String getTransitionString(int transitionType) {
switch (transitionType) {
case Geofence.GEOFENCE_TRANSITION_ENTER:
return "Entrando";
case Geofence.GEOFENCE_TRANSITION_EXIT:
return "Saindo";
default:
return "Desconhecido";
}
}
}
And the Broadcast receiver in the Activity:
public class GeofenceSampleReceiver extends BroadcastReceiver
{
/*
* Define the required method for broadcast receivers
* This method is invoked when a broadcast Intent triggers the receiver
*/
#Override
public void onReceive(Context context, Intent intent)
{
// Check the action code and determine what to do
String action = intent.getAction();
// Intent contains information about errors in adding or removing geofences
if (TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCE_ERROR"))
{
handleGeofenceError(context, intent);
// Intent contains information about successful addition or removal of geofences
}
else if (
TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCES_ADDED")
||
TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCES_REMOVED"))
{
handleGeofenceStatus(context, intent);
// Intent contains information about a geofence transition
} else if (TextUtils.equals(action, "br.com.marrs.imhere.ACTION_GEOFENCE_TRANSITION"))
{
handleGeofenceTransition(context, intent);
// The Intent contained an invalid action
}
else
{
Log.e("DEBUG", "Invalid action detail");
Toast.makeText(context, "Invalid action detail", Toast.LENGTH_LONG).show();
}
}
/**
* If you want to display a UI message about adding or removing geofences, put it here.
*
* #param context A Context for this component
* #param intent The received broadcast Intent
*/
private void handleGeofenceStatus(Context context, Intent intent) {
}
/**
* Report geofence transitions to the UI
*
* #param context A Context for this component
* #param intent The Intent containing the transition
*/
private void handleGeofenceTransition(Context context, Intent intent) {
/*
* If you want to change the UI when a transition occurs, put the code
* here. The current design of the app uses a notification to inform the
* user that a transition has occurred.
*/
}
/**
* Report addition or removal errors to the UI, using a Toast
*
* #param intent A broadcast Intent sent by ReceiveTransitionsIntentService
*/
private void handleGeofenceError(Context context, Intent intent)
{
String msg = intent.getStringExtra("br.com.marrs.imhere.EXTRA_GEOFENCE_STATUS");
Log.e("DEBUG", msg);
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}
}
And here is the piece of code that I use to create a GEofence before send to GeofenceRequester.
int raio = Integer.parseInt(spinner.getAdapter().getItem(spinner.getSelectedItemPosition()).toString());
int transitionType = (in.isChecked())?Geofence.GEOFENCE_TRANSITION_ENTER:Geofence.GEOFENCE_TRANSITION_EXIT;
Geofence geofence = new Geofence.Builder().setRequestId(nomeGeofence.getText().toString()).setTransitionTypes(transitionType).setCircularRegion(lat, lon, raio).setExpirationDuration(Geofence.NEVER_EXPIRE).build();
geofences.add(geofence);
try
{
mGeofenceRequester.addGeofences(geofences);
addCircleGeofence(raio);
}
catch (UnsupportedOperationException e)
{
Toast.makeText(getActivity(), "Já existe uma requisição de add em andamento",Toast.LENGTH_LONG).show();
}
Any help will be great!
Thanks!
I had the same exact problem. Here is what I answered over there: So after playing around with this a bit, it looks like the ReceiveTransitionsIntentService (as defined in the sample code) will stop getting the notifications when the app is not around. I think this is a big problem with the example code... Seems like that will trip folks like me up.
So I used a broadcast receiver instead, and so far it seems to be working from my tests.
Add this to the manifest:
<receiver android:name="com.aol.android.geofence.GeofenceReceiver"
android:exported="false">
<intent-filter >
<action android:name="com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE"/>
</intent-filter>
</receiver>
Then in the GeofenceRequester class you need to change the createRequestPendingIntent method so that it goes to your BroadcastReceiver instead of the ReceiveTransitionsIntentService. MAKE SURE AND NOTE the change to .getBroadcast instead of getService. That got me hung up for a while.
private PendingIntent createRequestPendingIntent() {
// If the PendingIntent already exists
if (null != mGeofencePendingIntent) {
// Return the existing intent
return mGeofencePendingIntent;
// If no PendingIntent exists
} else {
// Create an Intent pointing to the IntentService
Intent intent = new Intent("com.aol.android.geofence.ACTION_RECEIVE_GEOFENCE");
//MAKE SURE YOU CHANGE THIS TO getBroadcast if you are coming from the sample code.
return PendingIntent.getBroadcast(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
}
Then I added the GeofenceReceiver class that looks something like this:
public class GeofenceReceiver extends BroadcastReceiver {
Context context;
Intent broadcastIntent = new Intent();
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
if (LocationClient.hasError(intent)) {
handleError(intent);
} else {
handleEnterExit(intent);
}
}
private void handleError(Intent intent){
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(
context, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_error_detail,
errorMessage));
// Set the action and error message for the broadcast intent
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(context).sendBroadcast(
broadcastIntent);
}
private void handleEnterExit(Intent intent) {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
// Post a notification
List<Geofence> geofences = LocationClient
.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,
geofenceIds);
String transitionType = GeofenceUtils
.getTransitionString(transition);
for (int index = 0; index < geofences.size(); index++) {
Geofence geofence = geofences.get(index);
...do something with the geofence entry or exit. I'm saving them to a local sqlite db
}
// Create an Intent to broadcast to the app
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_TRANSITION)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_ID, geofenceIds)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_TRANSITION_TYPE,
transitionType);
LocalBroadcastManager.getInstance(MyApplication.getContext())
.sendBroadcast(broadcastIntent);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG, transitionType + ": " + ids);
Log.d(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_notification_text));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, "transition");
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_invalid_type,
transition));
}
}
/**
* Posts a notification in the notification bar when a transition is
* detected. If the user clicks the notification, control goes to the main
* Activity.
*
* #param transitionType
* The type of transition that occurred.
*
*/
private void sendNotification(String transitionType, String locationName) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent = new Intent(context, MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions
// >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(transitionType + ": " + locationName)
.setContentText(
context.getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
}
Hopefully that helps someone else.
I had a similar issue and after trying various ways I could finally fix the issue. Unfortunately, the android documentation fails to mention these issues.
The android geofences get removed every time you reboot your device or every time you toggle the location mode.
So Add a broadcast receiver to listen to device reboots and location mode changes, and add the geofences again in the receiver.
I have posted a detailed explanation here
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