My WidgetConfigActivity creates an onClickListener PendingIntent to pass through RemoteViews to perform two tasks: (1) open SliderActivity and (2) pass the appropriate appWidgetId.
val views =RemoteViews(context.packageName, R.layout.widget)
views.setOnClickPendingIntent(
R.id.tv_widget_access_slider,
getSliderPendingIntent(this, appWidgetId)
)
fun getSliderPendingIntent(context: Context, appWidgetId: Int): PendingIntent {
val intent =Intent(context, SliderActivity::class.java)
intent.putExtra("appWidgetId", appWidgetId)
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) //tried this but didn't help
Log.d("APP WIDGET PENDING INTENT", "$appWidgetId")
return PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
}
I need to get the appWidgetId in my SliderActivity so that the correct data can be displayed. So, I get the intent in onCreate.
val appWidgetId =intent.extras?.getInt("appWidgetId", 0) ?: 1
Log.d("APP WIDGET ID RECEIVED", "$appWidgetId")
intent.removeExtra(AppWidgetManager.EXTRA_APPWIDGET_ID) //tried this but didn't help
It works except for one thing. By logging, I've learned that the same appWidgetId is always received in SliderActivity onCreate even though unique appWidgetIds are added to the PendingIntent
First widget added to home screen Pending intent id =187, Slider onCreate id =187
Second widget added to home screen Pending intent id =188, Slider onCreate id =187
Third widget added to home screen Pending intent id =189, Slider onCreate id =187
How can I get the correct appWidgetId to my widget onClickListner to my SliderActivity?
Each distinct app widget needs a distinct PendingIntent, where "distinct" is largely determined by the ID that you pass as the second parameter to the PendingIntent.getActivity() method. If you use the same ID for multiple app widgets, they all wind up using the same PendingIntent, despite code that otherwise looks like it is creating three PendingIntent objects.
If you need to change the contents of the Intent for a PendingIntent, use FLAG_UPDATE_CURRENT as part of your flags in the PendingIntent.getActivity() call. Otherwise, an existing PendingIntent for that ID will be left alone.
IOW, think of PendingIntent.getActivity() as being a lazy-create mechanism — you need to take steps to force it to give you distinct objects and to update what is in them.
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'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
Background:
I'm using PendingIntent for alarms via AlarmManager.
The problem:
At first I thought that in order to cancel previous ones, I must provide the exact requestCode that I've used before to start the alarm.
But then I've found out I was wrong, as the cancellation API says:
Remove any alarms with a matching Intent. Any alarm, of any type,
whose Intent matches this one (as defined by filterEquals(Intent)),
will be canceled.
looking at "filterEquals", the documentation says:
Determine if two intents are the same for the purposes of intent
resolution (filtering). That is, if their action, data, type, class,
and categories are the same. This does not compare any extra data
included in the intents.
so I don't get what the "requestCode" is for...
The question:
What is "requestCode" used for?
What if I create multiple alarms with the same "requestCode" ? do they override each other?
requestCode is used to retrieve the same pending intent instance later on (for cancelling, etc).
Yes, my guess is the alarms will override each other. I would keep the request codes unique.
I just want to add to #Minhaj Arfin answer
1- requestCode is used to get the same pending intent later on (for cancelling etc)
2- Yes, they will get override as long as your specify the same Receiver to your Intent that you specify on your PendingIntent
example:
Intent startIntent1 = new Intent(context, AlarmReceiverFirst.class);
PendingIntent pendingIntent1 = PendingIntent.getBroadcast(context, 0, startIntent1, 0);
Intent startIntent2 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(context, 0, startIntent2, 0);
From above example, they will not override each other because the receiver is different(AlarmReceiverFirst and AlarmReceiverSecond)
Intent startIntent2 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent2 = PendingIntent.getBroadcast(context, 0, startIntent2, 0);
Intent startIntent3 = new Intent(context, AlarmReceiverSecond.class);
PendingIntent pendingIntent3 = PendingIntent.getBroadcast(context, 0, startIntent3, 0);
From above example, they will override each other, because the receiver is same(AlarmReceiverSecond)
Actually, the documentation clearly states what the request code is used for:
If you truly need multiple distinct PendingIntent objects active at
the same time (such as to use as two notifications that are both shown
at the same time), then you will need to ensure there is something
that is different about them to associate them with different
PendingIntents. This may be any of the Intent attributes considered by
Intent#filterEquals(Intent), or different request code integers
supplied to getActivity(Context, int, Intent, int),
getActivities(Context, int, Intent[], int), getBroadcast(Context, int,
Intent, int), or getService(Context, int, Intent, int).
Since it seems that it still isn't that clear, let me try to explain:
When you want to use a PendingIntent object, you don't just instantiate one. Rather, you obtain one from the system using the PendingIntent static methods (getActivity, getBroadcast, getService etc). The system keeps a bunch of PendingIntent instances and gives you one. Which one it gives you, it depends on the input parameters you pass to these getter methods. Those input parameters are: Context, i.e. the target receiver of the intent, the Intent to use, requestCode and flags. When you pass the same Context, the same requestCode and the same Intent (meaning an intent that filterEquals with another intent), you get the same PendingIntent object. The point is that the system wants to have as few PendingIntent objects as possible, so it tends to reuse the existing ones, as much as possible.
For example, you have two calendar notifications, for two different dates. When you click on one of them, you want your app to open to the corresponding date of that notification. In that scenario, you have the same Context target, and the Intent object you are passing differ only in the EXTRA_DATA (which specifies the date that should be open). If you provide the same requestCode when obtaining the PendingIntent object, then you will end up with the same PendingIntent object. So, when creating the second notification, you will replace the old Intent object with the new EXTRA_DATA, and end up with two notifications pointing to the same date.
If you want to have two different PendingIntent objects, as you should in this scenario, you should specify a different requestCode when obtaining the PendingIntent object.
in my case i want to open the same activity with two different intents so if two or more FCMS are there in the tray, any one of them will only open other will not, so i changed the requests codes of pending intent then it worked.
PendingIntent pendingIntent =
PendingIntent.getActivity(this, **Some unique id for all GCMS** /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
one important thing about requestCode that will seriously trouble your app is when using widgets.
widgets will not work after phone reboot if their requestCode are the same.
that means the pendingIndent you set on the remoteViews of your widget must be set unique requestCode, usually the widgetId accompanying a number.
i have one widget in which there are two buttons and one service class in which i have two methods on clikcing of button i want to call the methods in service class.
any reference or code example?
how can i do it?
thankx
I'm guessing that your question pertains to appWidgets. If not, the answer below will probably be less useful, although you can still adapt part of it to your own use, I think.
First declare some action(s), like this:
public static final String ACTION_HIDE_GUIDE_OVERLAY = "dk.something.appwidget.calendar.guide.hide";
Use the action(s) to declare an Intent and a PendingIntent:
Intent intent = new Intent(context, CalendarService.class);
intent.setAction(CalendarService.ACTION_HIDE_GUIDE_OVERLAY);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent buttonPendingIntent1 = PendingIntent.getService(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then register the pendingIntent with the RemoteView for your widget:
views.setOnClickPendingIntent(R.id.calendar_button_1, buttonPendingIntent1);
Finally, in the onStartCommand or onHandleIntent of your service, examine the action of the intent to determine which of your methods to call:
if (intent.getAction().equals(CalendarService.ACTION_INITIALIZE_CALENDAR)) {
int[] appWidgetIds = intent.getIntArrayExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS);
onInitializeCalendar(appWidgetIds);
} else if (intent.getAction().equals(CalendarService.ACTION_HIDE_GUIDE_OVERLAY)) {
int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
onHideGuideOverlay(appWidgetId);
} else ...
Note: The code snippets are taken almost directly from my Glass Widgets, so to adapt it to your own use, you would have to replace the action constant with your own two actions (one action for each button), replace R.id.calendar_button_1 with the actual id of one of your buttons, replace the CalendarService with the name of your own service class and the methods onInitializeCalendar and onHideGuideOverlay with the name of the two methods in your service class that you want to call.
If you only ever have one instance of your widget visible at one time, then you don't need to fiddle around with the appWidgetId, but you probably can't guarantee that. I have some settings that can vary from widget instance to widget instance, so I put the id of the appwidget that was clicked into the intent, where the service can get at it when servicing my request.
I have a WidgetProvider and an Configure Activity
When the Widget is started it starts with the configure activity and I set it up by making a custom call to the widgetprovider
(which you will notice is from the sdk tutorial examples)
// Push widget update to surface with newly set prefix
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
AwarenessWidget.updateAppWidget(context, appWidgetManager,
mAppWidgetId, position);
// Make sure we pass back the original appWidgetId
Intent resultValue = new Intent();
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mAppWidgetId);
setResult(RESULT_OK, resultValue);
finish();
I pass the Widget ID to the function.... inside the widget I create a Intent like this:
Intent configIntent = new Intent(context, Configure.class);
configIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
PendingIntent pendingIntent = PendingIntent.getActivity
(context, 0, configIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.MainImage,pendingIntent);
views.setImageViewResource(R.id.MainImage, lv_images[version]);
appWidgetManager.updateAppWidget(appWidgetId, views);
I am always referencing the widget ID and even add it as a extra on the intent
but when I get two of these widgets on the home screen the widget ID is always referencing the last placed widget ID
I had a similar problem. Just add this to your config activity, where you set your PendingIntent:
Uri data = Uri.withAppendedPath(
Uri.parse(URI_SCHEME + "://widget/id/")
,String.valueOf(appWidgetId));
intent.setData(data);
The variable URI_SCHEME is a String, and can be whatever you'd like.. ie - "ABCD" This causes each widget to have a unique PendingIntent.
Here is a more in-depth explanation of why your code doesn't work and how to fix it. From the Android SDK Documentation:
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.
Because of this behavior, it is important to know when two Intents are
considered to be the same for purposes of retrieving a PendingIntent.
A common mistake people make is to create multiple PendingIntent
objects with Intents that only vary in their "extra" contents,
expecting to get a different PendingIntent each time. This does not
happen. The parts of the Intent that are used for matching are the
same ones defined by Intent.filterEquals. If you use two Intent
objects that are equivalent as per Intent.filterEquals, then you will
get the same PendingIntent for both of them.
Note that specifying differing "extra" contents isn't enough for the PendingIntents to be considered unique, but setting a unique URI with setData is. That is why Snailer's URI solution "magically" fixes the problem.
The documentation also offers a different (arguably simpler) solution to the problem. Instead of creating a custom URI just set a unique requestCode when you call getActivity:
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, configIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Source: http://developer.android.com/reference/android/app/PendingIntent.html
In my testing, using the setData(...) on the PendingIntent doesn't fix the issue on a Verizon Thunderbolt running Android 4.0.4. It works on my other test devices and emulator.
I tested the use of the requestCode instead, and it works in all cases. I just set the requestCode to be the widget ID:
pendingIntent = PendingIntent.getService(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);