I am developing an app,which is publishing messages and subscribe using Google Nearby Messages API.The documentation says Because the Nearby Messages APIs have the potential to impact battery life, they should only be used from a foreground activity (with the exception of BLE background subscribe).
But is it still possible?
And what strategy to use to achieve maximum distance?
Thx.
Answer to your question, on the official documentation
Sample GitHub Google
Google Solution
// Subscribe to messages in the background.
private void backgroundSubscribe() {
Log.i(TAG, "Subscribing for background updates.");
SubscribeOptions options = new SubscribeOptions.Builder()
.setStrategy(Strategy.BLE_ONLY)
.build();
Nearby.getMessagesClient(this).subscribe(getPendingIntent(), options);
}
private PendingIntent getPendingIntent() {
return PendingIntent.getBroadcast(this, 0, new Intent(this, BeaconMessageReceiver.class),
PendingIntent.FLAG_UPDATE_CURRENT);
}
The following code snippet demonstrates handling the intent in the BeaconMessageReceiver class.
#Override
public void onReceive(Context context, Intent intent) {
Nearby.getMessagesClient(context).handleIntent(intent, new MessageListener() {
#Override
public void onFound(Message message) {
Log.i(TAG, "Found message via PendingIntent: " + message);
}
#Override
public void onLost(Message message) {
Log.i(TAG, "Lost message via PendingIntent: " + message);
}
});
}
When the subscription is no longer required, your app should unsubscribe by calling Nearby.getMessagesClient(Activity).unsubscribe(PendingIntent).
A solution to use BLE
SubscribeOptions.Builder builder = new SubscribeOptions.Builder();
if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
builder.setStrategy(Strategy.BLE_ONLY);
} else {
builder.setStrategy(new Strategy.Builder().setDistanceType(Strategy.DISTANCE_TYPE_EARSHOT).build());
Toast.makeText(this, "BLE NOT SUPPORTED", Toast.LENGTH_SHORT).show();
}
mOptions = builder.build();
Publish
Strategy s = new Strategy.Builder()
.setDistanceType(Strategy.DISTANCE_TYPE_EARSHOT)
.build();
PublishOptions options = new PublishOptions.Builder()
.setStrategy(s)
.build();
Nerby.getMessagesClient(this).publish(mMessageName, options);
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 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() {
...
});
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 am looking for the best way to notify a user that they need to install a package by handling the possibility of an external package not being found.
In this particular case I am wishing to implement the TODO: if intent not found, notification on need to have GSF in the C2DMMessaging class
public static void register(Context context,
String senderId) {
Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
registrationIntent.setPackage(GSF_PACKAGE);
registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
registrationIntent.putExtra(EXTRA_SENDER, senderId);
context.startService(registrationIntent);
// TODO: if intent not found, notification on need to have GSF
}
I'm thinking that I should look for the error W/ActivityManager( 60): Unable to start service Intent { act=com.google.android.c2dm.intent.REGISTER pkg=com.google.android.gsf (has extras) }: not found
But how to trap that error?
UPDATE - Just found that the call to startService returns a ComponentName instance which is null if the service failed to start so my code now looks like this
ComponentName name = context.startService(registrationIntent);
// TODO: if intent not found, notification on need to have GSF
if(name == null){
Util.log_debug_message("#### REG INTENT FAILED");
}else{
Util.log_debug_message("#### REG INTENT SUCCEEDED");
}
(For anyone looking for this solution Util.log_debug is just a function I created in a util class to call Log.d so just replace this with a call to Log.d)
Which seems to work just fine so I guess I need to send a broadcast message with an extra to indicate the package needs to be installed. The receiver could then show an alert dialog explaining that the user must install!
What does the user need to install? and how would the user install whatever is needed to be installed?
Thanks in advance for any tips, pointers code snippets and help
I have resolved this issue - In the activity that calls the C2DM registration event I have this code
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
register();
}
protected void register() {
String reg_id = C2DMessaging.getRegistrationId(this);
String email = Util.getEmail(this);
if(reg_id == null || reg_id == ""){
Util.log_debug_message("#### Registering with C2DM");
Toast.makeText(this, "Registering with C2DM", Toast.LENGTH_LONG).show();
if(C2DMessaging.register(this, Config.C2DM_SENDER)){
showLoadingDialog();
}else{
showInstallGSFDialog();
}
}else if(email == null || email =="-1"){
Util.log_debug_message("**** Updating server with new auth token");
register_with_server();
}
}
private void showInstallGSFDialog(){
AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setTitle("ERROR!");
alertDialog.setMessage("Please ensure you have a valid GMail account set up on your phone." +
" This application needs to use Google's C2DM service");
alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
} });
alertDialog.show();
}
and I changed the C2DMessaging.register to a boolean method and added the check to ensure that the service started like so...
/**
* Initiate c2d messaging registration for the current application
*/
public static boolean register(Context context, String senderId) {
boolean res = false;
Intent registrationIntent = new Intent(REQUEST_REGISTRATION_INTENT);
registrationIntent.setPackage(GSF_PACKAGE);
registrationIntent.putExtra(EXTRA_APPLICATION_PENDING_INTENT,
PendingIntent.getBroadcast(context, 0, new Intent(), 0));
registrationIntent.putExtra(EXTRA_SENDER, senderId);
ComponentName name = context.startService(registrationIntent);
// if intent not found, notification on need to have GSF by NOT setting resukt of this function to true
if(name == null){
Util.log_debug_message("#### REG INTENT FAILED");
}else{
Util.log_debug_message("#### REG INTENT SUCCEEDED");
res = true;
}
return res;
}