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 :)
Related
I thought this would solve my problem, but it doesn't.
I have this code to send my alarm:
public void triggerAlarm() {
AlarmManager alarmManager = (AlarmManager) getContext().getSystemService(ALARM_SERVICE);
alarmManager = (AlarmManager) getContext().getSystemService(ALARM_SERVICE);
Intent intent = new Intent(getContext(), AlarmReceiver.class);
intent.putExtra("Id", nextDue.id.get() + "");
String passed = intent.getStringExtra("Id");
Log.d("DEBRRUG", "The extra im passing: " + passed);
PendingIntent pendingIntent = PendingIntent.getBroadcast(getContext(), i++, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, soonest.dueTime.get(), pendingIntent);
}
My DEBRRUG statement indicates that the extra being passed is 8.
This is my alarm receiver:
#Override
public void onReceive(Context context, Intent intent) {
String passed = intent.getStringExtra("Id");
Log.d("DEBRRUG", "The extra im receiving: " + passed);
}
Here, my DEBRRUG statemnt indicates that the extra im receiving is NULL.
Note: Something that could, possibly, be interesting is that my triggerAlarm method is being called from within my ContentProvider. Don't know if that helps you understand my problem better or not.
Use
intent.getExtras().getString("id")
I found it. Wow! I was sending another alarm in another area of my code, where I wasn't putting the extra on.
Doing ctrl f -> ".set(Alar" let me track down the little bastard.
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[].
I am scheduling 2 alarms for an Event. First in one hour before and the second in the Event time.
It works in Android M and lower.
In Android N the Bundle(extras) comes empty.
This is how I create an intent
public static Intent createEventIntent(final Context context, final Event event) {
final Intent intent = new Intent(context, AlarmReceiver.class);
intent.putExtra(EXTRA_ALARM_TYPE, EXTRA_EVENT_TYPE);
intent.putExtra(EXTRA_EVENT, event);
return intent;
}
Event implements Serializable.
Then, I create a pending Intent
final int broadCastId = event.hashCode();
final int broadCastId2 = broadCastId - 1; //to ensure 2 alarms
final PendingIntent alarmIntent =
PendingIntent.getBroadcast(mContext, broadCastId, intent, FLAG_UPDATE_CURRENT);
final PendingIntent alarmIntentOneHourBefore =
PendingIntent.getBroadcast(mContext, broadCastId2, intent, FLAG_UPDATE_CURRENT);
Then I schedule the alarms:
mAlarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), alarmIntent);
...
mAlarmManager.set(AlarmManager.RTC_WAKEUP, calendarOneHourBefore.getTimeInMillis(),
alarmIntentOneHourBefore);
In the method onReceive, the extras are null:
#Override
public void onReceive(final Context context, final Intent intent) {
final String type = intent.getStringExtra(EXTRA_ALARM_TYPE); // null in 7.0
}
This is happening only in Android N.
What am I missing?
I have tried:
Event implements parcelable and then send a parcelable.
Create a Bundle and add the extras to the Bundle.
Set and intent action.
I remember seeing something about tightening restrictions on use of custom Parcelable objects in AlarmManager Intents. I can't find the reference now. You should figure out a way to rearchitect your app so that you don't send a custom Parcelable to the AlarmManager.
You can serialize your custom Parcelable to a simple String or a byte[] and put that in the Intent and that should solve your problem.
I want to set up a repeating alarm which sends on its first trigger with Bundles the string "test".
Is it possible on the next trigger to have another string like "test2" ?
Alarm manager:
// Enable the scheduled alarm to send notifications
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent alarmIntent = new Intent(this, Service.class);
Bundle extras = new Bundle();
extras.putString("test", "test");
alarmIntent.putExtras(extras);
PendingIntent alarmPendingIntent = PendingIntent.getService(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
// Set the alarm in 30 minutes and repeat it every 30 minutes.
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 1000,
1000,
alarmPendingIntent);
IntentService:
public class Service extends IntentService {
public Service() {
super("service");
}
#Override
protected void onHandleIntent(Intent intent) {
String test = intent.getStringExtra("test");
// update the bundle data
Log.i("result", test);
// how to update here the string to "test2" ?
}
}
I want on the second trigger the value of String test to be test2.
But I always get the original value from the first trigger.
You will need separate alarms and separate PendingIntents. You can either set them up at the same time, or set an alarm that does not repeat and have the service set the following alarm whenever it runs.
Note that if you set the two alarms at the same time, you need something other than the extras to be different because Android does not compare intent extras when it checks for an existing PendingIntent for the given Intent. One way to do this is like so:
Intent alarmIntent = new Intent(this, Service.class);
...
alarmIntent.putExtras(extras);
alarmIntent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
I register alarms which I schedule to execute at given time, and it can be many alarms depending on the size of the scheduled list. But I have two questions which remains unclear to me:
1) How can I query the OS for the Pending Intents I registers? I need this for testing. The psudo code for what I want would be something like this:
List<PendingIntent> intentsInOS = context.getAllPendingIntentsOfType(AppConstants.INTENT_ALARM_SCHEDULE));
2) See the pending intent I create, I provide an action and extra data (the schedule id).
private Intent getSchedeuleIntent(Integer id) {
Intent intent = new Intent(AppConstants.INTENT_ALARM_SCHEDULE);
intent.putExtra(AppConstants.INTENT_ALARM_SCHEDULE_EXTRA, id);
return intent;
}
But we also say that the intent have FLAG_CANCEL_CURRENT. Will it cancel all pending intents with same action, or does it have to both same action AND extra data?
PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, getSchedeuleIntent(schedule.id), PendingIntent.FLAG_CANCEL_CURRENT);
My code
#Override
public void run() {
List<ScheduledLocation> schedules = dbManager.getScheduledLocations();
if(schedules == null || schedules.isEmpty()){
return;
}
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
//alarmManager.
// we need to get the number of milliseconds from current time till next hour:minute the next day.
for(ScheduledLocation schedule : schedules){
long triggerAtMillis = DateUtils.millisecondsBetweenNowAndNext(now, schedule.hour, schedule.minute, schedule.day);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(), 0, getSchedeuleIntent(schedule.id), PendingIntent.FLAG_CANCEL_CURRENT);
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, MILLISECONDS_IN_WEEK, pendingIntent);
}
// List<PendingIntent> intentsInOS = context.getAllPendingIntentsOfType(AppConstants.INTENT_ALARM_SCHEDULE));
}
private Intent getSchedeuleIntent(Integer id) {
Intent intent = new Intent(AppConstants.INTENT_ALARM_SCHEDULE);
intent.putExtra(AppConstants.INTENT_ALARM_SCHEDULE_EXTRA, id);
return intent;
}
1 How can I query the OS for the Pending Intents I registers?
I'm not sure you can, but you can check if a specific PendingIntent is registered or not like this :
private boolean checkIfPendingIntentIsRegistered() {
Intent intent = new Intent(context, RingReceiver.class);
// Build the exact same pending intent you want to check.
// Everything has to match except extras.
return (PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_NO_CREATE) != null);
}
2 Will it cancel all pending intents with same action, or does it have to both same action AND extra data?
It will cancel all the PendingIntent that are resolved to be equal.
What exactly does equal means ?
The java doc of android 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.
You can read at the 7391 line here : https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/content/Intent.java
To sum up, all PendingIntent that are build exactly the same except extras will be cancelled.