I'd like to ask you for help as after trying to figure this issue out for a couple of hours still can't get it works.
I have a notification manager which process incoming GCM messages and creates notifications, however an intent, that is passed to pending intent, always got old extras (intent recycle) within activity.
intent.putExtra("user_id", id);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
builder.setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT));
Lets say that I will receive two notifications from two different users and intent starts the same activity which displays that user ID. After click on first notification, activity is launched as usually and intent's extras contains user ID of first user. However, if I will remain within this activity, and click on another push notification, an activity is recreated (onDestroy is called) but, intent's extras contains user id of first user, not the second one.
Here is my question. How can I retrieve new intent extras? I've already tried to implement onNewIntent callback method, but it never get called, also tried to change flags but unsuccessfully and what's kinda weird to me is, that even after onDestroy callback is called, intent extras in next instance of that activity have old data...
Thanks in advance
You need to pass unique Id in place of just 0 when fetching Activity from PendingIntent:
int iUniqueId = (int) (System.currentTimeMillis() & 0xfffffff);
builder.setContentIntent(PendingIntent.getActivity(context, iUniqueId, intent, PendingIntent.FLAG_UPDATE_CURRENT));
I think you fire your notifications either with the same id or with a inappropriate intent flag. As it is mentioned here, when creating the pending intent you can set its flag. If you don't like the previous pending intent to be updated or overridden, you should set its flag to FLAG_ONE_SHOT. It indicates that although you have more than one pending intents sticking around in the system, each can be executed only once!
Conclusion: Your code should be sth like this:
PendingIntent pIntent = PendingIntent.getActivity(context, id,intent,PendingIntent.FLAG_ONE_SHOT);
In this code the "id" is unique per pending intent and "intent" is the actual intent for the target activity.
Cheers
Related
I have a question regarding proximity alerts.
In all tutorials I ve read they are created and destroyed while the activity that create them is still running.
But what happens if say an activity creates n proximity alerts and then the activity itself is destroyed (the PA are not)
Then if I want to build another activity that finds these Proximity Alerts, how can I do that? Is that even possible?
You have to maintain your own list of proximity alerts. There is no way to get them back. However, #Mercato is correct when he says that you can remove a PA using only pending intents, but you don't have to store them. According to 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.
This means that the system will store your PendingIntent for you between app restarts, and you can retrieve it by passing the same Intent you used to create it. So for example, if you created the following PendingIntent:
Intent intent = new Intent(context, Foo.class);
PendingIntent pi = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then all you have to store is the requestId (1) and the Class or class name (Foo.class or Foo.class.getName()). Then if you want to retrieve that same PendingIntent without creating a new one, you can do the following:
Class<Foo> className = retrieveClass(); //You implement this
//String clazz = retrieveClassName(); //This is another option
int requestId = retrieveId(); //You implement this
Intent intent = new Intent(context, className);
//The flag given attempts to retrieve the PendingIntent if it exists, returns null if it doesn't.
PendingIntent pi = PendingIntent.getBroadcast(context, requestId, intent, PendingIntent.FLAG_NO_CREATE);
if (pi != null) {
//This pending intent was registered once before.
//Go ahead and call the function to remove the PA. Also, go ahead and call pi.cancel() on this.
}
else {
//This pending intent was not registered, and therefore can't have a PA registered to it.
}
Technically, all proximity alerts need a PendingIntent defined and used as a parameter. Android's Documentation shows that if you know the list of PendingIntents then you can remove them as well.
removeProximityAlert(PendingIntent intent) Removes the proximity alert
with the given PendingIntent.
Since PendingIntent is Parecelable see here then you could add it as an Extra to any Intent. This means, that on starting another Activity, you can create an Parcelable[] array to hold all these PendingIntent, then
putExtra(String name, Parcelable[] value)
Add extended data to the intent.
then retrieve them in the next Activity via getIntent() and it's relevant methods.
I'm notifying user about some events (in my case SMS receiving) and hanging PendingIntent like:
intent = new Intent(context, ConversationActivity.class);
intent.setAction(Constants.ACTION_SMS_RECEIVED);
intent.putExtra(MessageDAO.CONVERSATION_ID, message.getConversationId());
intent.putExtra(MessageDAO._ID, message.getId());
intent.putExtra(MessageDAO.ADDRESS, message.getAddress());
contentIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT );
My purpose is to start new ConversationActivity if there's no one, either just reuse old one (refreshing its content). But in fact my PendingIntent just use old one not refreshing it's content. I have checked that old extras still send to my ConversationActivity
What is the problem? I'm really stuck with it and can't understand what to do...
Update
I'm extracting extras in target ConversationActivity.onCreate() like:
Bundle bundle=this.getIntent().getExtras();
if(bundle!=null) {
conversationId=bundle.getString(MessageDAO.CONVERSATION_ID);
address=bundle.getString(MessageDAO.ADDRESS);
}
If you want to reuse an existing activity, you need to do the following. When creating the notification, add this:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
Then you will need to override onNewIntent() in ConversationActivity. This will be called (instead of onCreate() if the user clicks on a notification and there is already an active ConversationActivity. You will need to extract the "extras" from the Intent in onNewIntent() and replace the content with this new data.
I've got some weird behavior and I can only assume is because of the Pending intents I am using.
Scenario
I have a widget (4x1) which has 4 buttons. Within onUpdate of the widget, I add an pending intent for each button. My intents fires a Service with a bundeled parameter and depending on this parameter starts something. I attach intents as this:
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
Bundle b = new Bundle();
b.putString("myVariable", someVariable);
Intent intent = new Intent(context, AppStarterService.class);
intent.putExtras(b);
PendingIntent pendingIntent = PendingIntent.getService(context, buttopnPosition, intent, 0);
views.setOnClickPendingIntent(R.id.btnOne, pendingIntent);
The problem
The code works just fine, until the user decides to update the content of the button. Then, a new Pending Intent is done. So, when I press again the button, sometimes it still executes the old intent and not the new one. I don't know how to explain this better. Let's say for my first intent the parameter is "TestOne", after my update, the new intent has parameter "TestX". When the user clicks on the button, on my service I get in intent extras still "TestOne" instead "TestX". So, my guess is that somehow, I need to cancel the previous intent, when the widget button content changes.
Is this the issue ? Am I doing something wrong ? How do I cancel the old intent, I need to recreate it and then cancel it ?
Thank you for your time.
I you keep having this problem even with FLAG_UPDATE_CURRENT, try defining a different requestCode each time, with something like this:
private static int request = 0;
...
PendingIntent pendingIntent = PendingIntent.getService(context, request++, intent, 0);
So each time a new PendingIntent is created, a new requestCode is used, at least during class life.
I hope it helps.
I think you want to set the flag http://developer.android.com/reference/android/app/PendingIntent.html#FLAG_UPDATE_CURRENT as the last parameter to PendingIntent.getService
This questions somehow relates to the question when I was looking to get the extras back in startActivityForResult but now I face another challenge.
I have subscribed to receive ProximityAlerts and I have explicitly constructed the Intent to include some Extras. But when I got the service the extras are not there.
After the answers here is the working code:
Intent intent = new Intent(this, PlacesProximityHandlerService.class);
intent.setAction("PlacesProximityHandlerService");
intent.putExtra("lat", objPlace.getLat());
intent.putExtra("lon", objPlace.getLon());
intent.putExtra("error_m", objPlace.getError()+ALERT_RANGE_IN_METERS);
PendingIntent sender=PendingIntent.getService(this, 0, intent, 0);
LocationUtils.addProximity(this, objPlace.getLat(), objPlace.getLon(),objPlace.getError()+ALERT_RANGE_IN_METERS, -1, sender);
The documentation says param PendingIntent to be sent for each location update
For some unspecified reason, extras will be delivered only if you've set some action, for example setAction("foo"). What CommonsWare refers to applies only when obtaining PendingIntent instances, if you haven't set FLAG_ONE_SHOT. That can be fixed by the requestCode argument in PendingIntent.get... factory methods. Although documentation says it's currently not used, it actually takes into count when distinguishing PendingIntents.
In your case, you don't need to set anything else than some dummy action string. LocationManagerService reuses the PendingIntent you have subscribed for proximity alerts, and only adds a flag if phone has entered or exited the alarm range.
If you have multiple outstanding PendingIntents, you need to make sure that the underlying Intents differ on more than their extras. Otherwise, Android will keep reusing the first PendingIntent you created for your first Intent, using that first Intent's extras all of the time.
For example, you could add a unique action via setAction() -- that will not change your Intent routing (since you are specifying the component), but it will make your Intents different.
I had this problem and the solution I found was quite simple, though I can't explain why it worked.
Initially my pending intent looked like this:
notificationIntent = new Intent(ctx, FragmentTabsPager.class);
notificationIntent.setData(Uri.parse("content://com.sbs.mobile.workorder.WorkOrder/notes/"));
notificationIntent.putExtra("NOTIFICATION", true);
notificationIntent.putExtra(WorkOrder.WorkOrderColumns.WORKORDERID, submessage);
When creating the intent like this, no extras would be passed when the notification was clicked, the extras map would be empty in the receiving activity. I made the following change to the line initializing the notificationIntent:
notificationIntent = new Intent().setClass(ctx, FragmentTabsPager.class);
Now the extras are populated in the receiving activity. Again, I can't explain why this works but it fixed my problem.
None of the answers worked for me. Setting action to a specific string works for the first time but if you use the same notification with different extras at a later time, it would not work. I replaced the string for the setAction method with a randomly generated one and it works without any issues:
intent.setAction(new Random().nextInt(50) + "_action");
If you think that you might use the notification a lot (Like for downloading different files) then pass a larger number to nextInt()
The key is to set the extras and the unique action into the intent before calling
PendingIntent sender=PendingIntent.getService(this, 0, intent, 0);
if you set the extras and action into the intent after calling the above, it won't work.
This will not work:
Intent intent;
PendingIntent sender=PendingIntent.getService(this, 0,
intent=new Intent(this, PlacesProximityHandlerService.class), 0);
intent.setAction("PlacesProximityHandlerService");
intent.putExtra("lat", objPlace.getLat());
A few days ago I was struggling to find a way to use custom intents for my alarms. Although I got clear answer that I have to customize the Intents based on some unique ID eg. setAction() still have some problems.
I define a PendingIntent this way:
Intent intent = new Intent(this, viewContactQuick.class);
intent.setAction("newmessage"+objContact.getId());//unique per contact
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK ).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP );
intent.putExtra("id", Long.parseLong(objContact.getId()));
intent.putExtra("results", result.toArray());
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, intent, 0);
then this is used by a notification manager
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(ns);
// first try to clear any active notification with this contact ID
mNotificationManager.cancel(Integer.parseInt(objContact.getId()));
// then raise a new notification for this contact ID
mNotificationManager.notify(Integer.parseInt(objContact.getId()), notification);
This works like this:
application creates a message for a contact
an intent is provided with the contact id and details about the message
notification is raised with the message
user actiones on the notification and the app displays the message passed by the intent
The problem
This can happen more than once for a contact. And when the second message is generated, the notification is raised well (message is fine there) but the intent when the user actions the notification it uses old data, so previous message is passed and not the brand new message.
So someway the intent is caching and reusing previous extras. How can I make it unique per contact and per action?
If only one of your PendingIntents for this contact will be outstanding at any point in time, or if you always want to use the latest set of extras, use FLAG_UPDATE_CURRENT when you create the PendingIntent.
If more than one contact-specific PendingIntent will be outstanding at once, and they need to have separate extras, you will need to add a count or timestamp or something to distinguish them.
intent.setAction("actionstring" + System.currentTimeMillis());
UPDATE
Also, the lightly-documented second parameter to getActivity() and kin on PendingIntent apparently can be used to create distinct PendingIntent objects for the same underlying Intent, though I have never tried this.
I usually specify unique requestCode to prevent my PendingIntents from overriding each other:
PendingIntent pending = PendingIntent.getService(context, unique_id, intent, 0);
And in your case I agree with CommonsWare you just need FLAG_UPDATE_CURRENT flag. New extras will override old values.