Search code examples
android-geofence

Android Geofence eventually stop getting transition intents


I have an app that started with the Google's geofencing sample code. It works great for a few days, and I get all the transition intents as I anticipate. However, after a bit of time, something like 3 days, the app stops getting these intents, and I don't know why.

When I create my fences, I'm setting the expiration duration to Geofence.NEVER_EXPIRE

Here is my IntentService where I get the transition intents before they stop working:

public class ReceiveTransitionsIntentService extends IntentService {
    @Override
    protected void onHandleIntent(Intent intent) {

        Intent broadcastIntent = new Intent();

        broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);

        // First check for errors
        if (LocationClient.hasError(intent)) {
                ...handle errors
        } 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
                NEVER GETS HERE
            } else {
                ...log error
            }
        }
    }
}

Here is pertinent part of the manifest:

<service
            android:name="com.aol.android.geofence.ReceiveTransitionsIntentService"
            android:exported="false" >
        </service>

In my GeofenceRequester class, it is almost identical to the sample code. Here are the pertinent parts:

// 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);

private PendingIntent createRequestPendingIntent() {

            // Create an Intent pointing to the IntentService
            Intent intent = new Intent(context, ReceiveTransitionsIntentService.class);

            return PendingIntent.getService(
                    context,
                    0,
                    intent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
        }
    }

Can anyone see why this would stop working?


Solution

  • 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

    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");
    //            Intent intent = new Intent(context, 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.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.