Android notification addAction not launching broadcast - android

Creating notification:
PendingIntent pIntent = PendingIntent.getActivity(context, (int) taskId, intent, 0);
intent.setAction(Utils.MARK_AS_DONE);
PendingIntent pIntentMarkAsDone = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setTicker(ticker)
.setContentTitle(title)
.setContentText(description)
.setSmallIcon(getAlarmIcon(type))
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),R.mipmap.ic_launcher))
.setContentIntent(pIntent)
.addAction(0, context.getString(R.string.mark_as_done), pIntentMarkAsDone);
Notification notification = builder.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify((int) taskId, notification);
I added the adding using a pending intent with getBroadcast.
Receiver:
public class NotificationReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// Log to check
}
}
This class should "receive" the action. I also add on Manifest
Manifest:
<receiver android:name=".NotificationReceiver">
<intent-filter>
<action android:name="<package_name>.MARK_AS_DONE"/>
</intent-filter>
</receiver>
Well, onReceive is not receiving. What am I doing wrong?

TL;DR: Create a fresh Intent, rather than reusing the one in intent, and get rid of the <intent-filter> from the <receiver>.
Your first line is:
PendingIntent pIntent = PendingIntent.getActivity(context, (int) taskId, intent, 0);
This implies that intent identifies some activity. If you created this via new Intent(context, YourActivityClass.class), then it has a ComponentName set inside of it, identifying your activity.
Then, you call setAction() on intent and use it with getBroadcast(). However, other than setting (or replacing) the action, everything else in intent is the same as it was. In particular, the ComponentName identifying the activity is still there. So, when the broadcast is sent, Android cannot deliver it, as the component is invalid (an activity cannot directly receive a broadcast), and the action string is ignored (as once a ComponentName is set on an Intent, things like actions and categories no longer count for routing).
So, I recommend that you create two Intent objects, one for the activity, one for the receiver.
Note that you do not need an action string for the receiver. You can use the explicit Intent constructor (new Intent(context, NotificationReceiver.class)). In fact, having the action string on the receiver is bad for security, as now any app can send you that broadcast. So, I recommend removing the <intent-filter> and using an explicit Intent to create your broadcast PendingIntent.

Related

Set alarm from notification

I need to add an alarm to mobile's clock app for which I'm sending user a notification. When user clicks on notification a new alarm should be added with given time.
Below is the code:
//Create intent
Intent alarmIntent = new Intent(AlarmClock.ACTION_SET_ALARM);
alarmIntent.putExtra(AlarmClock.EXTRA_MESSAGE, event.getEventName());
Calendar alarmTime = new GregorianCalendar();
alarmTime.setTime(new Date(event.getAlarmTime()));
alarmIntent.putExtra(AlarmClock.EXTRA_HOUR, alarmTime.get(Calendar.HOUR_OF_DAY));
alarmIntent.putExtra(AlarmClock.EXTRA_MINUTES, alarmTime.get(Calendar.MINUTE));
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, 0);
//Create and show notification
NotificationManager mNotificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel("MyAppsAlarm",
"MyAppsAlarmNotifications",
NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("Channel to show notifs");
mNotificationManager.createNotificationChannel(channel);
NotificationCompat.Builder builder = new NotificationCompat.Builder(main.getApplicationContext(), "Zzzzz")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Alarm Helper")
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setContentIntent(alarmPendingIntent);
mNotificationManager.notify(0, builder.build());
When I click on notification nothing happens. Notification stays as it is while the notification drawer closes automatically.
I tried to fire the intent using startActivity(alarmIntent); and it works as expected but from notification .setContentIntent(alarmPendingIntent); seems to be doing nothing.
If you want to set an alarm using AlarmClock.ACTION_SET_ALARM then you have to use PendingIntent.getActvity() instead of PendingIntent.getBroadcast(). AlarmClock.ACTION_SET_ALARM is an Activity ACTION.
If you don't want the UI of the alarm clock to be shown, you can add this to the Intent:
alarmIntent.putExtra(AlarmClock.EXTRA_SKIP_UI, true);
You have to use broadcast receiver in your app to receive broadcast when the user clicks on notification.
Let your broadcast receiver is NotifBroadCastReceiver
public class NotifBroadCastReceiver extends BroadcastReceiver{
#override
void onReceive(Context context, Intent intent){
//you can extract info using intent.getStringExtra or any other method depending on your send data type. After that set alarm here.
}
}
So when create pending intent you can do
Intent intent = new Intent(context, BroadcastReceiver.class);
//set all the info you needed to set alarm like time and other using putExtra.
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
Now when user click on notification, you will receive the broadcast in onReceive of NotifBroadCastReceiver.
NOTE You have to register your broadcast receiver in manifest like
<receiver
android:name="your broadcast receiver"
android:enabled="true"
android:exported="false" />

How to dismiss notification without intent?

I want to display a notification inside the app that disappears when the notification is tapped without starting an activity.
I use an empty intent and it works:
Intent intent = new Intent();
PendingIntent contentIntent = PendingIntent.getActivity(context, (int)System.currentTimeMillis(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context, "")
.setSmallIcon(R.drawable.icon)
.setContentTitle(title)
.setContentText(text)
.setAutoCancel(true)
.setTicker(text);
.setContentIntent(contentIntent);
However, there are some crashes:
java.lang.RuntimeException: bad array lengths
at android.os.Parcel.readIntArray(Parcel.java:789)
at android.app.INotificationManager$Stub$Proxy.enqueueNotificationWithTag(INotificationManager.java:339)
at android.app.NotificationManager.notify(NotificationManager.java:139)
at android.app.NotificationManager.notify(NotificationManager.java:112)
...
According to this SO answer the crashes seem to happen because intent is not starting an activity.
A notification can be dismissed by its ID, but I cannot dismiss it when there is no activity to start and call the dismiss method.
How can I dismiss the notification on tap inside the app without using an empty intent?
Update:
According to this SO questions it seems to be an Android issue. The crashes I got reported also happened on Android 4.3.
Update 2:
According to this SO answer it seems to be due to a too large bitmap set with setLargeIcon. So I am reducing the bitmap size now with Glide.
You can create an intent which calls a BroadcastReceiver which then cancels the notification.
Your PendingIntentcan be created like this:
private PendingIntent getCancelNotificationIntent() {
Intent cancelIntent = new Intent(context, CancelNotification.class);
return PendingIntent.getBroadcast(context, 0, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
The CancelNotification.java class would look similar to this:
public class CancelNotification extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
notificationManager.cancel(NOTIFICATION_ID);
}
}
EDIT:
NOTIFICATION_ID is a constant you define and pass to the NotificationManager like this:
notificationManager.notify(NOTIFICATION_ID, yourNotificationObject);
NOTE: don't forget to register the receiver in your manifest file like this:
<receiver android:name="com.example.package.CancelNotification" />
If you don't want to start an activity when users tap the notification you can build an pending intent which sends a broadcast or start a service.
Here is an example:
PendingIntent.getBroadcast(context, 12, new Intent("any intent action"), PendingIntent.FLAG_UPDATE_CURRENT)
But what prevents your activity to have implementation like this:
#Override
public void onCreate(Bundle savedInstanceState) {}
with additional nodisplay theme in manifest:
android:theme="#android:style/Theme.NoDisplay"
It'd be simply a no-op one.

Android Notification Action PendingIntent.getBroadCast does not work

My Android notification Action buttons are not working at all. I have the following code in my service, and the receiver is NOT registered in the manifest because it makes no change. I can send the broadcast from another activity, and it works great, but there is a problem somewhere.
Here are the PendingIntents that pair with the buttons
Intent next = new Intent(getString(R.string.receiver_notification_media_change));
next.setAction(NOTIFICATION_MEDIA_CHANGE_NEXT);
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(getApplicationContext(), 0, next, PendingIntent.FLAG_UPDATE_CURRENT);
Intent last = new Intent(getString(R.string.receiver_notification_media_change));
last.setAction(NOTIFICATION_MEDIA_CHANGE_BACK);
PendingIntent pendingIntentLast = PendingIntent.getBroadcast(getApplicationContext(), 0, last, PendingIntent.FLAG_UPDATE_CURRENT);
Notification:
Notification.Builder mBuilder = new Notification.Builder(getApplicationContext())
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setSmallIcon(smallDrawableResId)
.addAction(R.drawable.icon1, "as", pendingIntentLast)
.addAction(R.drawable.icon2, "asdf", pendingIntentNext)
.setContentTitle(title)
.setContentText("title")
.setLargeIcon(icon)
.setContentIntent(pendingIntent) //to an activity. Works great
.setOngoing(true)
.setStyle(new Notification.MediaStyle()
.setShowActionsInCompactView(0, 1));
Here is the BroadcastReceiver which is declared in the class below.
private BroadcastReceiver notificationMediaChanger = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
System.out.println("RECEIVEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD");
if(action.equals(NOTIFICATION_MEDIA_CHANGE_NEXT))
playNextSong();
else if(action.equals(NOTIFICATION_MEDIA_CHANGE_BACK))
playPreviousSong();
}
};
OnCreate the receiver is registered
registerReceiver(notificationMediaChanger, new IntentFilter(getString(R.string.receiver_notification_media_change))); //LocalBroadcastManager.getInstance(getApplicationContext()) appears to be equivalent.
And OnStop it is removed:
unregisterReceiver(notificationMediaChanger);
Your action strings do not match.
Intent next = new Intent(getString(R.string.receiver_notification_media_change));
next.setAction(NOTIFICATION_MEDIA_CHANGE_NEXT);
For some reason, you are replacing one action string with another. I do not know why.
registerReceiver(notificationMediaChanger, new IntentFilter(getString(R.string.receiver_notification_media_change)));
Here, you are using the first action string. Your Intent has the second action string. These are presumably not the same.
Also:
LocalBroadcastManager is not used by PendingIntent
Unless the Notification is only on the screen while your activity is on the screen (which would be bizarre), you need to register your receiver in the manifest

Android: Notifications, PendingIntent, and actions

I have a question related to creating notifications in Android with an attached action. My goal is to have an action that won't re-open my app but will simply perform some logic as specified by a class in my app. Here is my code to create said notification.
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Intent notificationIntent = new Intent(context, RetryReceiver.class);
final PendingIntent retryIntent = PendingIntent.getBroadcast(context, notificationId, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
final NotificationCompat.Builder mNotifyBuilder = new NotificationCompat.Builder(context)
.setContentTitle(title)
.setTicker(ticker)
.setContentText(message)
.setSmallIcon(R.drawable.notifcation_sprout_leaf)
.setLargeIcon(largeIcon)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
mNotifyBuilder.addAction(R.drawable.refresh_action_bar, "Retry", retryIntent);
}
// Creates an explicit intent for an Activity in your app
Intent mainIntent = new Intent(context, MainActivity.class);
// The TaskStackBuilder needs multiple intents in case there are multiple failures in succession
// Thus default it to have a MainActivity intent it can fall back on
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(mainIntent);
stackBuilder.addNextIntent(composeIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(notificationId, PendingIntent.FLAG_UPDATE_CURRENT);
mNotifyBuilder.setContentIntent(resultPendingIntent);
// Because the ID remains unchanged, the existing notification is updated.
notificationManager.notify(notificationId, mNotifyBuilder.build());
Here is my class to receive the broadcast:
public class RetryReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
final Bundle bundle = intent.getExtras();
// do shit
}
}
I have also registered the receiver in the AndroidManifest.xml as such:
<receiver
android:name=".RetryReceiver"
android:enabled="true"
android:exported="true" >
</receiver>
For some reason, the code in my receiver is never being fired, anyone have any suggestions?
You need to call setContentIntent(retryIntent) on your mNotifyBuilder - it is not set automatically.
Attribute android:exported="true" is to alow the broadcast receiver receive messages from sources outside its application.
The absence of any filters means that it can be invoked only by Intent objects that specify its exact class name. This implies that the receiver is intended only for application internal use.
So android:exported should be declared as android:exported="false" or not declared because it defaults to false in that case.
As I can't see no other problem with your code. Please try with android:exported="false"
See Receiver Android documentation

Create timed notification (e.g. for events) in Android

For some Android applications, I would like to integrate the following feature:
The user can define a time when he wants to be reminded of something. When the time has come then, the application should create a notification in the notification bar even when the user doesn't use the app at this moment.
For this purpose, the classes AlarmManager, NotificationManager und Notification.Builder are the ones to look at, right?
So how do I create a timed notification in advance? My code (so far) is this:
Add this under to the AndroidManifest to register the broadcast receiver:
<receiver android:name="AlarmNotificationReceiver"></receiver>
Create a new class file which handles the alarm that it receives:
public class AlarmNotificationReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
if (extras != null) {
String additionalData = extras.getString("displayText");
// show the notification now
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification mNotification = new Notification(R.drawable.ic_launcher, context.getString(R.string.app_name), System.currentTimeMillis());
PendingIntent pi = PendingIntent.getActivity(context, 0, new Intent(context, MainActivity.class), 0); // open MainActivity if the user selects this notification
mNotification.setLatestEventInfo(context, context.getString(R.string.app_name), additionalData, pi);
mNotification.flags |= Notification.FLAG_AUTO_CANCEL | Notification.DEFAULT_SOUND;
mNotificationManager.notify(1, mNotification);
}
}
}
Use this code (for example in MainActivity) to set the alarm to 3 seconds from now:
Intent i = new Intent(this, AlarmNotificationReceiver.class);
i.putExtra("displayText", "sample text");
PendingIntent pi = PendingIntent.getBroadcast(this.getApplicationContext(), 234324246, i, 0);
AlarmManager mAlarm = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
mAlarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+3*1000, pi);
What do I need to change to make this work? Thank you!
The two problems are:
The notification's text does not change when I change it in code. It only changes when I change the requestCode in PendingIntent.getBroadcast(...). What is this request code all about? Can it be a random value or 0?
After rebooting my phone, the "planned" notification, or the alarm, is gone. But now I've seen that this is normal behaviour, right? How can I circumvent this?
Not sure about part 1, but for part 2 the general approach is to intercept the BOOT_COMPLETED intent and use that to re-register all alarms. This does unfortunately mean that for each alarm you have registered with the alarm manager you have to store it in your app's db as well.
So, you'll need a broadcast receiver to intercept the BOOT_COMPLETED intent:
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// get your stored alarms from your database
// reregister them with the alarm manager
}
}
To get the BOOT_COMPLETED intent, you must put the following permission in your manifest:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
And the BootReceiver also needs to be registered in your manifest with the following intent filter:
<receiver android:enabled="true" android:name=".receiver.BootReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
It's important to note that if your app is installed to the sdcard, it can never receive the BOOT_COMPLETED intent. Also, it's worth noting that this implementation is a bit naive in that it executes code immediately on booting which can slow the user's phone down at startup. So, I recommend delaying your execution for a few minutes after intercepting the boot intent.
I personally would do it without a Broadcast Receiver. I'd get the AlarmManager to fire the intent to start a seperate Activity, rather than receiver. Then this new Activity could make the notification for you. I'm not sure if this is a better way, but it seems less complicated to me.
Edit: A Service would probably be better still than an Activity
In your MainActivity:
Intent i = new Intent(getBaseContext(), NotificationService.class);
PendingIntent pi = PendingIntent.getService(getBaseContext(), 0, i, 0);
AlarmManager mAlarm = (AlarmManager) Context.getSystemService(Context.ALARM_SERVICE);
mAlarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+10*60*1000, pi);
Your Service:
public class NotificationService extends Service {
public int onStartCommand(Intent intent, int flags, int startId) {
//Create the notification here
return START_NOT_STICKY;
}
Your Manifest:
<service android:name="com.android.yourpath.NotificationService"></service>

Categories

Resources