4

my application perform periodic location updates and Activity recognition detection in background.

I'm doing that using the Google Play Services API's:

for example - to register to location updates, I provide pending intent to receive update:

mLocationClient.requestLocationUpdates(mLocationRequest, pendingInent);

to unregister to location update, I'm doing the following:

mLocationClient.removeLocationUpdates(pendingInent);

that's nice, and working great.

but how can I find out if there is currently a pendingIntent holding Intent to my application's component is currently registered in front of Google play services to receive location updates or not?

I notices that there is no API to check this, and the approach to check for pendingIntent existence seems not to work for API's against google play services - the pending intent stays exists even after I call the removeLocationUpdates() method.

I know I can save state (to shared preferences) indicating if now I'm registered or not, but it's not the right solution, and will be "buggy" because it can happen that google play process went down, lost the pendingIntent, but my process will still think that the location updates are "active"..

same problem exactly exists for the activity recognition updates.

Just to make it clear, all I want to do is provide to the users of my application ability to know if my app is currently collecting data in background or not, and provide them a way to toggle between that. So if there is other way to do that in reliable way - I'll except it as an answer also

reliable way = knowing if currently the pending intent really registered in from of google play services...

  • using LocationListener is not an option for me, because I must be able to receive updates event when my process is shout down - what's possible only using PendingIntent callback..

Thanks in advance.

UPDATE

this is the pendintIntent I provide to register and unregister to location updates:

Intent locationUpdatesIntent = new Intent(context, LocationUpdatesIntentService.class);
PendingIntent pendingInent = PendingIntent.getService(context, 0, locationUpdatesIntent, PendingIntent.FLAG_UPDATE_CURRENT);

and this is how I'm trying to check (unsuccessfully) if it registered or not:

Intent locationUpdatesIntent = new Intent(context, LocationUpdatesIntentService.class);
PendingIntent pendingInent = PendingIntent.getService(context, 0, locationUpdatesIntent, PendingIntent.FLAG_NO_CREATE);
boolean isLocationUpdatesEnabled = (pendingIntent != null);

isLocationUpdatesEnabled returns true event after I call removeLocationUpdates()

Community
  • 1
  • 1
Tal Kanel
  • 10,475
  • 10
  • 60
  • 98
  • are you using `com.google.android.gms.location.LocationClient`? – Ogen Aug 07 '14 at 11:43
  • According to the docs, LocationClient is deprecated and you should be using LocationServices instead. See here: https://developer.android.com/reference/com/google/android/gms/location/LocationClient.html – Ogen Aug 07 '14 at 11:45
  • Doesn't answer your question but just a heads up. – Ogen Aug 07 '14 at 11:45
  • Can you show the code used to create `PendingIntent`? Mainly interested in seeing the flags passed to it. – Manish Mulimani Aug 14 '14 at 04:52
  • @ManishMulimani: sure, I updated my question. I'll be glad if you'll have a look – Tal Kanel Aug 14 '14 at 05:05

2 Answers2

2

Following are the scenarios when a PendingIntent is removed:

  • PendingIntent can be removed explicitly by calling PendingIntent.cancel, only by application which created it. From the docs:

Only the original application owning a PendingIntent can cancel it.

  • PendingIntent, if inactive i.e. not being used by other application, is removed by the system when the application terminates.
  • PendingIntent, if active i.e. being used by other application, is removed by the system only when the application package is uninstalled.

Hence calling removeLocationUpdates indicates the system that Google Location services no more references the PendingIntent. It does not remove it from the system. So you will have to call cancel on PendingIntent to remove it, which is absolutely fine.

mLocationClient.removeLocationUpdates(pendingInent);
pendingIntent.cancel();

//Now check whether PendingIntent exists.
Intent locationUpdatesIntent = new Intent(context, LocationUpdatesIntentService.class);
PendingIntent pendingInent = PendingIntent.getService(context, 0, locationUpdatesIntent, PendingIntent.FLAG_NO_CREATE);
boolean isLocationUpdatesEnabled = (pendingIntent != null);

Additional info from the docs:

A PendingIntent itself is simply a reference to a token maintained by the system describing the original data used to retrieve it. This means that, even if its owning application's process is killed, the PendingIntent itself will remain usable from other processes that have been given it. If the creating application later re-retrieves the same kind of PendingIntent (same operation, same Intent action, data, categories, and components, and same flags), it will receive a PendingIntent representing the same token if that is still valid, and can thus call cancel() to remove it.

Manish Mulimani
  • 17,535
  • 2
  • 41
  • 60
  • your solution sounds good and promising, I'll try it as soon as I can, and came back to reward you if really adding pendingIntent.cancel() works as you desribed :-> – Tal Kanel Aug 16 '14 at 12:09
  • just checked it. looking great! as you said, calling cancel() seems to do the job! thank you very much for your answer!! I was already a bit skeptic there is really solution to that.. great to learn something new! – Tal Kanel Aug 16 '14 at 12:41
0

Just to make it clear, all I want to do is provide to the users of my application ability to know if my app is currently collecting data in background or not, and provide them a way to toggle between that. So if there is other way to do that in realible way - I'll except it as an answer also

Inform the user the app is collecting background data with a custom notification in the status bar. The notification will take the user to where they can gracefully stop the recording.

The example below is part of a service. Start and stop are controlled by a boolean passed with the intent used to start the service. When your app pauses the service is still recording background data and the notification can take the user back to your app to gracefully shut down the background data recording.

When recording startForeground to display status bar notification. When not recording stopForeground(true) to remove the notification.

boolean isRecording = false;

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent != null) {
        // intent is processing = b
        boolean b = intent.getBooleanExtra(SettingRecord.RECORDING,
                isRecording);
        //custom method to start recording that changes the isRecording flag.
        startRecording(b);
    }

    if (isRecording) {
        startForeground(R.id.action_record, getCustomNotification());
        return Service.START_STICKY;
    } else {
        stopForeground(true);
        return Service.START_NOT_STICKY;
    }
}


private static Notification getCustomNotification() {
    Context context = weakContext.get();
    if (context == null) {
        return null;
    } else {
        // calculate the progress.
        int progress = numberOfLocations * 100 / MAX_LOCATIONS;
        if (progress > 100) {
            progress = 100;
        }

        String trackedMsg;
        if (isMiles) {
            trackedMsg = "Tracked "
                    + decimalFormat.format(distance * METERS_TO_MILES)
                    + " miles";
        } else {
            trackedMsg = "Tracked "
                    + decimalFormat.format(distance * METERS_TO_KILOMETERS)
                    + " km";
        }

        // build the notification.

        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
                context)
        // notification 1
                .setContentTitle("Best Rides")
                // notification 2
                .setLargeIcon(
                        BitmapFactory.decodeResource(
                                context.getResources(),
                                R.drawable.ic_launcher))
                // notification 4 number of locations
                .setContentInfo(trackedMsg)
                // notification 5
                .setSmallIcon(R.drawable.ic_launcher2)
                // notification 6 time of locations

                // user can't cancel the recording message because it
                // contiually
                // updates
                .setOngoing(isRecording)
                // autocancel the notification so it appears the servcice
                // has
                // stopped
                .setAutoCancel(true);

        // notification 3 name of route
        if (progress == 100) {
            mBuilder.setTicker("Tracker Full")
                    // show the route recorder is full
                    .setContentText("Tracker Full")
                    .setPriority(NotificationCompat.PRIORITY_LOW);
            startRecording(false);
        } else if (isRecording) {
            mBuilder.setContentText("Tracking route")
                    // uses default progress bar
                    .setProgress(100, progress, false)
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT);
        } else {
            mBuilder
            // show the route recorder is not tracking
            .setContentText("Not Tracking").setPriority(
                    NotificationCompat.PRIORITY_LOW);
        }

        // link the notifications to the recorder activity
        Intent resultIntent = new Intent(context, KmlReader.class);
        resultIntent
                .setAction(ServiceLocationRecorder.INTENT_COM_GOSYLVESTER_BESTRIDES_LOCATION_RECORDER);
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent resultPendingIntent = PendingIntent
                .getActivity(context, 0, resultIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);

        return mBuilder.build();
    }
}

@Override
public void onCreate() {
    ServiceLocationRecorder.weakContext = new WeakReference<Context>(
            getApplicationContext());
    locationListener = (LocationListener) this;
    connectionCallbacks = (GooglePlayServicesClient.ConnectionCallbacks) this;
    onConnectionFailedListener = (OnConnectionFailedListener) this;
    // get the notification manager
    // get the db distance
}


private static WeakReference<Context> weakContext = null;

private static LocationClient locationClient;
private static int numberOfLocations = 0;

private static LocationListener locationListener;
private static GooglePlayServicesClient.ConnectionCallbacks connectionCallbacks = null;
private static OnConnectionFailedListener onConnectionFailedListener = null;
danny117
  • 5,581
  • 1
  • 26
  • 35
  • as I mentioned, my problem is not to display to the user, but to know if it really registered right now to updates. I don't want to use foreground service with LocationListener , because then if my process went down - google won't be able to wake my application. that's why I must user pendingIntent as a callback, and not a LocationListener callback. – Tal Kanel Aug 11 '14 at 14:41
  • I thought I had everything covered ability to know if app is collecting data (notification) with a way provided to toggle (notification intent). Not collecting data no notification. – danny117 Aug 11 '14 at 19:48
  • Thank you very much for your answer. Besides the fact that I don't want to show to the user this undissmisable notification, this approach will not work for registration to location updates with pendingIntent callback, because pendingIntent can be still registred if my Service, and even my entire process is stopped – Tal Kanel Aug 11 '14 at 20:21
  • 1
    Sorry I couldn't help. Your going for pending intent so your process can be awakened by the location. That's a good reason. I'm just brain storming. You could putextra a sequential number in your pending intent. Store lastnumberreceived and lastnumbersent in static sequential variables and use the difference between the two to determine if intents have been lost. – danny117 Aug 11 '14 at 20:38
  • Thanks for sharing this ideas! I Appreciate it .. I'm hoping that there is more trivial and reliable/official straight forward solution without workaround tricks, and hope this bounty will bring this solution out – Tal Kanel Aug 11 '14 at 20:47