Intent extras are lost with a broadcast PendingIntent and AlarmManager.setAlarmClock() - android

I create a PendingIntent like this:
Intent intent = new Intent(context, AlarmReceiver.class);
intent.setAction("Foobar");
intent.putExtra(EXTRA_ALARM, alarm);
intent.putExtra(EXTRA_TRYTWO, tryTwo);
intent.putExtra(EXTRA_BEGAN_TIME, beganTime);
return PendingIntent.getBroadcast(
context, (int) alarm.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT
);
The alarm variable is a Parcelable. I schedule the PendingIntent like this:
PendingIntent alarmModifyPendingIntent = PendingIntent.getActivity(
context, 0, editIntent, PendingIntent.FLAG_CANCEL_CURRENT
);
am.setAlarmClock(
new AlarmManager.AlarmClockInfo(time, alarmModifyPendingIntent), pendingIntent
);
Where the variable pendingIntent is created as shown above.
The AlarmReceiver object receives an Intent in onReceive at the correct time. However, this Intent does not contain the extras that I have set. For instance intent.getParcelableExtra(EXTRA_ALARM) returns null.
This problem occurs with Android 7.0 (API level 24) at least, using an LG G5.
Using FLAG_CANCEL_CURRENT or FLAG_ONE_SHOT does not work either.

The alarm variable is a Parcelable.
It is not safe to put a custom Parcelable in an Intent that is delivered to another process. This is particularly true with AlarmManager on Android 7.0.
You need to replace that Parcelable with something else, such as a byte[], where you manually convert your Parcelable to/from that byte[].

Related

Do I need to explicitely specify class using intent.setClass?

I want to use AlarmManager to schedule a repeating task. Basically, I have this code:
Intent intent = new Intent(INTENT_ACTION_TICK);
// The following line prevents the broadcast receiver from being notified:
intent.setClass(context, MyScheduler.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, intervalInMs, intervalInMs, pendingIntent);
I register MyScheduler as a broadcast receiver in its constructor:
context.registerReceiver(this, new IntentFilter(INTENT_ACTION_TICK));
Everything works as expected (receiver is triggered) unless I add the intent.setClass. Fine with me, however, I distinctly remember reading that you should use explicit intents (intent.setClass) for security reasons.
Is this something I have to consider for my use case?

Android 7 intent extras missing

Does anyone know if there are any changes to how Android 7.0 (Nougat) handles intent extras compared to Android 6.0 (Lollipop)?
Long story short: my app works as intended on all versions from 4.1(16) to 6.0(23) but crashes on android 7.0(24)!
The app creates a pending intent with an intent to a custom broadcast receiver which has extras. However, on android 7 none of the extras are present in the intent received by the broadcast receiver.
MainActivity.java
Intent intent = new Intent(context, PollServerReceiver.class);
// TODO: Remove after DEBUGGING is completed!
intent.putExtra("TESTING1", "testing1");
intent.putExtra("TESTING2", "testing2");
intent.putExtra("TESTING3", "testing3");
// PendingIntent to be triggered when the alarm goes off.
final PendingIntent pIntent = PendingIntent.getBroadcast(context,
PollServerReceiver.REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Setup alarm to schedule our service runs.
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
alarm.setRepeating(AlarmManager.RTC_WAKEUP, firstRun, freqMilis, pIntent);
PollServerReceiver.java
Bundle extras = intent.getExtras();
Log.d(TAG, "onReceive: TESTING1 = " + extras.getString("TESTING1")); // null here
// None of the three "TESTING*" keys are there!
for (String key : extras.keySet()) {
Object value = extras.get(key);
Log.d(TAG, String.format("onReceive extra keys: %s %s (%s)", key, value.toString(), value.getClass().getName()));
}
Stack trace obviously gives the NullPointerException as the cause of crash.
It would not be so weird if it would crash among all versions, but in this case its the latest android only. Has anyone got any ideas please?
Note: I have tried creating pending intents with different flags including (0, PendingIntent.FLAG_UPDATE_CURRENT, PendingIntent.FLAG_CANCEL_CURRENT) still got the exact same result.
Putting a custom Parcelable in a PendingIntent has never been especially reliable, and it flat-out will not work in an AlarmManager PendingIntent on Android 7.0. Other processes may need to fill in values into the Intent, and that involves manipulating the extras, and that can't be done in any process but your own, since no other process has your custom Parcelable class.
This SO answer has a workaround, in the form of converting the Parcelable yourself to/from a byte[].
I had a similar problem but I think I found an easy solution. Put your data inside a Bundle and send that Bundle with your intent. In my case I wanted to send a serializable object with my intent.
Setup the alarm:
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, AlarmReciever.class);
Bundle bundle = new Bundle();
//creating an example object
ExampleClass exampleObject = new ExampleClass();
//put the object inside the Bundle
bundle.putSerializable("example", exampleObject);
//put the Bundle inside the intent
intent.putExtra("bundle",bundle);
PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
//setup the alarm
alarmManager.setExact(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
Receive the alarm:
public class AlarmReciever extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// get the Bundle
Bundle bundle = intent.getBundleExtra("bundle");
// get the object
ExampleClass exampleObject = (ExampleClass)bundle.getSerializable("example");
}
}
It worked fine for me. Hope it helps :)

Android Pending Intent to start service

So I have a service, in the onCreate() I setup 3 pending intents to start the same service, each having different extra data to specify the action. I'm creating a notification, and I want to use one pending intent as the click action, one as the dismiss action, and the third is for an alarm.
Intent iCancel = new Intent(this, BootService.class);
Intent iAlarm = new Intent(this, BootService.class);
Intent iDismiss = new Intent(this, BootService.class);
// each will have actions
iAlarm.putExtra(INTENT_ACTION, INTENT_ALARM_FIRED);
iCancel.putExtra(INTENT_ACTION, INTENT_CANCEL);
iDismiss.putExtra(INTENT_ACTION, INTENT_DISMISS);
PendingIntent piCancel = PendingIntent.getService(
this,
0,
iCancel,
Intent.FILL_IN_DATA);
mPiAlarm = PendingIntent.getService(this, 0, iAlarm, Intent.FILL_IN_DATA);
PendingIntent piDismiss = PendingIntent.getService(this, 0, iDismiss, Intent.FILL_IN_DATA);
mNotifyBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_action_about)
.setContentTitle(getString(R.string.app_name))
.setContentIntent(piCancel)
.setDeleteIntent(piDismiss);
The problem is all pending intents seem that have the same intent extra data, so when onStartCommand is launched no matter whether the notification was clicked or dismissed or neither, constant INTENT_CANCEL is received from intent.getIntExtra(INTENT_ACTION)
I believe it has something to do with the flags used in PendingIntent.getService(), i'm confused about which to use. I've tried using PendingIntent.FLAG_CANCEL_CURRENT, and UPDATE_CURRENT, neither seem to fix the issue but the result is different, I receive constant INTENT_ALARM_FIRED for every action.
How can I get each pending intent to have its own intent extra data?
Solution
I discovered an exact warning about this scenario right in the PendingIntent doc. http://developer.android.com/reference/android/app/PendingIntent.html
just changed my request codes and it works
This has to do with Intents being considered the same. See PendingIntent for more information.
One way to get around this would be to just vary the requestCode for each Intent. (The 2nd parameter in the getService call)

cancel all the alarms set using alarmmanager

I want to cancel all the alarms that are set..... I have searched a lot and tried a lot of things but nothing worked... When I set alarm for say after 2 minutes and then I cancel it, it will still fire after 2 minutes.....
Any help will be appreciated.
Method for creating alarms :
Intent intent = new Intent(c, AlarmReceiver.class);
intent.putExtra("task", task);
intent.putExtra("id", id);
PendingIntent pendingIntent = PendingIntent.getActivity(c, code, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
This is to cancel alarms :
Intent intent = new Intent(c, AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getActivity(c, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.cancel(pendingIntent);
When you schedule your alarms, your second parameter to getActivity() is code.
When you try to cancel your alarms, your second parameter to getActivity() is 0. This will only successfully cancel an alarm whose code was 0.
If you want to consistently cancel the alarms, you need to create equivalent PendingIntents, and that means, among other things, that the second parameter to getActivity() needs to be the same.
You are setting the alarm with request code (second parameter for getActivity) equals to code, while when you are cancelling the alarm, the second parameter is 0. If the value of code is not 0 the alarm wouldn't be cancelled.
In order to fire and cancel multiple alarms, you need to use unique value for requestCode (second parameter) in PendingIntent.getActivity
see this code that will set 3 Alarms using three unique request Codes,
for (int requestCode = 1; requestCode <= 3; requestCode++) {
PendingIntent pendingIntent = PendingIntent.getActivity(c, requestCode, intent,PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
}
You can cancel the Alarm using the same requestCode (and other parameters) that were used to set it.
The other parameters which are needed to matched are,
The "action" in the Intent must be the same (or both null). Otherwise they do not match.
The "data" in the Intent must be the same (or both null). Otherwise they do not match.
The "type" (of the data) in the Intent must be the same (or both null). Otherwise they do not match.
The "package" and/or "component" in the Intent must be the same (or both null). Otherwise they do not match. "package" and "component" fields are set for "explicit" Intents.
The list of "categories" in the Intent must be the same. Otherwise they do not match.
Please see this answer for more details.
To cancel an alarm you need no re-create the same PendingIntent and pass the same request code.
You can see my solution here
When you cancel alarm its intent must be same with intent which used for set alarm .
so just add following 2 line
intent.putExtra("task", task);
intent.putExtra("id", id);
So your final code will be
Intent intent = new Intent(c, AlarmReceiver.class);
intent.putExtra("task", task);
intent.putExtra("id", id);
PendingIntent pendingIntent = PendingIntent.getActivity(c, code, intent, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager am = (AlarmManager)c.getSystemService(Context.ALARM_SERVICE);
am.cancel(pendingIntent);

How to properly cancel pending alarms

I can't seem to find an answer to this, but what is the criteria for passing in a matching PendingIntent to be removed from an alarm? It it based on the just the name like com.blah.package.myclass or do the Extras matter?
For example, I'm doing something like this and all alarms fire the same intent:
public void setAlarm(Context context, long nextAlarmMillis) {
Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra("alarm", this);
PendingIntent sender = PendingIntent.getBroadcast(context, 1234 /* unused */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// Get the AlarmManager service
AlarmManager am = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
am.cancel(sender);
am.set(AlarmManager.RTC_WAKEUP, nextAlarmMillis, sender);
}
What happens is that the Alarm class can be altered, etc and passed in as an Extra. I am canceling the previous alarm, but I'm not sure if it's doing anything or if any left over alarms will remain.
Anyone know for sure?
Following is mentioned in AlarmManager documentation
Any alarm, of any type, whose Intent matches this one (as defined by filterEquals(Intent)), will be canceled.
Which, the filterEquals() is defined as:
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.
I think the easiest to do is you keep the reference of PendingIntent passed to AlarmManager, and pass it to cancel. Or have a factory method to construct such pi.

Categories

Resources