Android: Notifications, PendingIntent, and actions - android

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

Related

Android BroadcastReceiver firing with delay upon notification action click

I am trying to run a Retry routine upon notification action click, for this, I have created a BroadcastReceiver and also registered it in the AndroidManifest file.
While creating the notification with action I am using PendingIntent and setting that pending intent with the notification action.
When the app is running or in the background (not removed from the recent apps list) then the broadcast receiver is received instantly upon clicking the notification action. But after killing the app (removing it from the recent app list) the broadcast receiver takes some to fire.
Below are the code snippets for different components.
AndroidManifest file
<receiver android:name=".Receiver.RetryStatusBroadcastReceiver" android:enabled="true" android:exported="false"/>
Broadcast receiver
public class RetryStatusBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
}
}
Notification firing code
Intent retryIntent = new Intent(this, RetryStatusBroadcastReceiver.class);
PendingIntent retryPendingIntent = PendingIntent.getBroadcast(this.getApplicationContext(), uniqueId + 1, retryIntent, 0);
NotificationCompat.Action retryAction = new NotificationCompat.Action(R.drawable.ic_retry, "Retry", retryPendingIntent);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, getString(R.string.background_syncing_notification_channel_id))
.setSmallIcon(R.drawable.app_logo)
.setPriority(Notification.PRIORITY_HIGH)
.setDefaults(Notification.DEFAULT_ALL)
.setContentTitle(notificationTitle)
.setContentText(notificationMessage)
.setStyle(new NotificationCompat.BigTextStyle().bigText(notificationMessage))
.setAutoCancel(true)
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
.addAction(retryAction);
Intent intent = new Intent(this, HomeActivity.class);
PendingIntent activityPendingIntent = PendingIntent.getActivity(this, uniqueId, intent, PendingIntent.FLAG_ONE_SHOT);
notificationBuilder.setContentIntent(activityPendingIntent);
notificationBuilder.setAutoCancel(true);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.notify(uniqueId, notificationBuilder.build());
}
I have also tried extending my BroadcastReceiver from WakefulBroadcastReceiver but the result is the same.
I have also tried removing android:enabled and android:exported tags but nothing seems to work here.

BroadcastReceiver does not get data from notification button

I am trying to make notification with remove button, notifications are stored in SQLite DB and after the press, I want to remove "record" by id. Firstly I get a notification, store it to db , store unique id and pass it to a method which creates a notification. It gets fine in the method.
public static String CUSTOM_ACTION = "com.example.app.Services.REMOVE";
Intent snoozeIntent = new Intent(this, MyBroadcastReceiver.class);
snoozeIntent.setAction(CUSTOM_ACTION);
snoozeIntent.putExtra("NOT_WORKING", String.valueOf(id));
PendingIntent snoozePendingIntent =
PendingIntent.getBroadcast(this, 0, snoozeIntent, 0);
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
String channelId = getString(R.string.default_notification_channel_id);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(this, channelId)
.setDefaults(Notification.DEFAULT_ALL)
.setSmallIcon(R.drawable.ic_stat_bell)
.setContentTitle("Loggly")
.setContentText(messageBody)
.setAutoCancel(true)
.setContentIntent(pendingIntent)
.addAction(R.drawable.ic_delete_forever_black_24dp, "Remove", snoozePendingIntent);
then I have a broadcast receiver for handling data from notification. Here is how it looks in my manifest file.
<receiver android:name=".MyBroadcastReceiver" android:exported="false">
<intent-filter>
<action android:name="com.example.app.Services.REMOVE"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
And here is the broadcast receiver the value which I got from extras is always null ... I have been trying to find a solution, but with no result.
public class MyBroadcastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Bundle extras = intent.getExtras();
String id;
if (extras != null) {
id = extras.getString("NOT_WORKING");
String ns = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager) context.getSystemService(ns);
if (notificationManager != null) {
notificationManager.cancel(0);
}
Intent inten = new Intent(context, RemoveService.class);
inten.putExtra("NOT_WORKING", id);
context.startService(inten);
}
}
}
Finally, I am starting intent service, which should delete the "record" from the database, but when the broadcast receiver does not receive id it can't do anything.
I just created a sample notification on my side and the problem is the way you are creating the pendingIntent. Just add the proper flag to the pendingIntent parameter and it will work fine.
PendingIntent snoozePendingIntent = PendingIntent.getBroadcast(this, 0, snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT);
You can use FLAG_ONE_SHOT as well. If it satisfies your use case. You can go through different flags which can be used in PendingIntent

Android notification addAction not launching broadcast

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.

Up navigation using backstack not working while clicked from notification

I am opening an activity from notification, which opens fine.
However, I want to open it's parent activity while I click 'back button', currently it exits the application directly. I want it to navigate to HomeScreenActivity.
Here is manifest declaration -
<activity
android:name="com.discover.activities.MyTrialsActivity"
android:exported="true"
android:parentActivityName="com.discover.activities.HomeScreenActivity"
android:screenOrientation="portrait">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.discover.activities.HomeScreenActivity" />
</activity>
Here is my code to generate notification -
public static PendingIntent getAction(Activity context, int actionId) {
Intent intent;
PendingIntent pendingIntent;
intent = new Intent(context, MyTrialsActivity.class);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the back stack
//stackBuilder.addParentStack(HomeScreenActivity.class);
stackBuilder.addParentStack(HomeScreenActivity.class);
// Adds the Intent to the top of the stack
stackBuilder.addNextIntent(intent);
// Gets a PendingIntent containing the entire back stack
pendingIntent =
stackBuilder.getPendingIntent(0 /*request code */, PendingIntent.FLAG_ONE_SHOT);
/*pendingIntent = PendingIntent.getActivity(context, 0 *//* Request code *//*, intent,
PendingIntent.FLAG_UPDATE_CURRENT*//*|PendingIntent.FLAG_ONE_SHOT*//*);*/
return pendingIntent;
}
/**
* Create and show a simple notification containing the message.
*
* #param message Message to show in notification
*/
public static void sendNotification(Context context, String message, int actionId) {
PendingIntent pendingIntent = NotifUtils.getAction((Activity) context, actionId);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Title")
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setVibrate(new long[]{1000})
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
I you have everything set up correctly and it's still not working it might be that you need to uninstall and reinstall the app. It seems like some changes to the manifest are not updated properly when you run the app!
Solution -
I added my child activity as well in addParentStack(MyTrialActivity.class);
And it worked as expected.
I thought adding addNextIntent() should be doing that already, though it did not work that way..
I found the solution in android's documentation
// Intent for the activity to open when user selects the notification
Intent detailsIntent = new Intent(this, DetailsActivity.class);
// Use TaskStackBuilder to build the back stack and get the PendingIntent
PendingIntent pendingIntent =
TaskStackBuilder.create(this)
// add all of DetailsActivity's parents to the stack,
// followed by DetailsActivity itself
.addNextIntentWithParentStack(upIntent)
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setContentIntent(pendingIntent);
And,
Here is the link.
Also, see this answer for more references.
Try using startActivities(Context context, Intent[] intents),
Intent homeIntent = new Intent(context, HomeScreenActivity.class);
Intent newIntent = new Intent(context, MyTrialsActivity.class);
Intent[] intents = new Intent[]{homeIntent, newIntent};
ContextCompat.startActivities(context, intents);
So we can start multiple activities at same time, so while pressing Back button it will go to Home Page instead of quiting the application.

How to dismiss notification after action has been clicked

Since API level 16 (Jelly Bean), there is the possibility to add actions to a notification with
builder.addAction(iconId, title, intent);
But when I add an action to a notification and the action is pressed, the notification is not going to be dismissed.
When the notification itself is being clicked, it can be dismissed with
notification.flags = Notification.FLAG_AUTO_CANCEL;
or
builder.setAutoCancel(true);
But obviously, this has nothing to with the actions associated to the notification.
Any hints? Or is this not part of the API yet? I did not find anything.
When you called notify on the notification manager you gave it an id - that is the unique id you can use to access it later (this is from the notification manager:
notify(int id, Notification notification)
To cancel, you would call:
cancel(int id)
with the same id. So, basically, you need to keep track of the id or possibly put the id into a Bundle you add to the Intent inside the PendingIntent?
Found this to be an issue when using Lollipop's Heads Up Display notification. See design guidelines. Here's the complete(ish) code to implement.
Until now, having a 'Dismiss' button was less important, but now it's more in your face.
Building the Notification
int notificationId = new Random().nextInt(); // just use a counter in some util class...
PendingIntent dismissIntent = NotificationActivity.getDismissIntent(notificationId, context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
builder.setPriority(NotificationCompat.PRIORITY_MAX) //HIGH, MAX, FULL_SCREEN and setDefaults(Notification.DEFAULT_ALL) will make it a Heads Up Display Style
.setDefaults(Notification.DEFAULT_ALL) // also requires VIBRATE permission
.setSmallIcon(R.drawable.ic_action_refresh) // Required!
.setContentTitle("Message from test")
.setContentText("message")
.setAutoCancel(true)
.addAction(R.drawable.ic_action_cancel, "Dismiss", dismissIntent)
.addAction(R.drawable.ic_action_boom, "Action!", someOtherPendingIntent);
// Gets an instance of the NotificationManager service
NotificationManager notifyMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// Builds the notification and issues it.
notifyMgr.notify(notificationId, builder.build());
NotificationActivity
public class NotificationActivity extends Activity {
public static final String NOTIFICATION_ID = "NOTIFICATION_ID";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(getIntent().getIntExtra(NOTIFICATION_ID, -1));
finish(); // since finish() is called in onCreate(), onDestroy() will be called immediately
}
public static PendingIntent getDismissIntent(int notificationId, Context context) {
Intent intent = new Intent(context, NotificationActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra(NOTIFICATION_ID, notificationId);
PendingIntent dismissIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return dismissIntent;
}
}
AndroidManifest.xml (attributes required to prevent SystemUI from focusing to a back stack)
<activity
android:name=".NotificationActivity"
android:taskAffinity=""
android:excludeFromRecents="true">
</activity>
I found that when you use the action buttons in expanded notifications, you have to write extra code and you are more constrained.
You have to manually cancel your notification when the user clicks an action button. The notification is only cancelled automatically for the default action.
Also if you start a broadcast receiver from the button, the notification drawer doesn't close.
I ended up creating a new NotificationActivity to address these issues. This intermediary activity without any UI cancels the notification and then starts the activity I really wanted to start from the notification.
I've posted sample code in a related post Clicking Android Notification Actions does not close Notification drawer.
In new APIs don't forget about TAG:
notify(String tag, int id, Notification notification)
and correspondingly
cancel(String tag, int id)
instead of:
cancel(int id)
https://developer.android.com/reference/android/app/NotificationManager
In my opinion using a BroadcastReceiver is a cleaner way to cancel a Notification:
In AndroidManifest.xml:
<receiver
android:name=.NotificationCancelReceiver" >
<intent-filter android:priority="999" >
<action android:name="com.example.cancel" />
</intent-filter>
</receiver>
In java File:
Intent cancel = new Intent("com.example.cancel");
PendingIntent cancelP = PendingIntent.getBroadcast(context, 0, cancel, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Action actions[] = new NotificationCompat.Action[1];
NotificationCancelReceiver
public class NotificationCancelReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//Cancel your ongoing Notification
};
}
You will need to run the following code after your intent is fired to remove the notification.
NotificationManagerCompat.from(this).cancel(null, notificationId);
NB: notificationId is the same id passed to run your notification
You can always cancel() the Notification from whatever is being invoked by the action (e.g., in onCreate() of the activity tied to the PendingIntent you supply to addAction()).
Just put this line :
builder.setAutoCancel(true);
And the full code is :
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(android.R.drawable.ic_dialog_alert);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.google.co.in/"));
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
builder.setContentIntent(pendingIntent);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.misti_ic));
builder.setContentTitle("Notifications Title");
builder.setContentText("Your notification content here.");
builder.setSubText("Tap to view the website.");
Toast.makeText(getApplicationContext(), "The notification has been created!!", Toast.LENGTH_LONG).show();
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
builder.setAutoCancel(true);
// Will display the notification in the notification bar
notificationManager.notify(1, builder.build());
Just for conclusion:
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
Intent intent = new Intent(context, MyNotificationReceiver.class);
intent.putExtra("Notification_ID", 2022);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context,
0,
intent,
...);
Notification notification = new NotificationCompat.Builder(...)
...
.addAction(0, "Button", pendingIntent)
.build();
notificationManager.notify(2022, notification);
and for dismiss the notification, you have two options:
approach 1: (in MyNotificationReceiver)
NotificationManager manager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
manager.cancel(intent.getIntExtra("Notification_ID", -1));
approach 2: (in MyNotificationReceiver)
NotificationManagerCompat manager = NotificationManagerCompat.from(context);
manager.cancel(intent.getIntExtra("Notification_ID", -1));
and finally in manifest:
<receiver android:name=".MyNotificationReceiver" />
builder.setAutoCancel(true);
Tested on Android 9 also.

Categories

Resources