I have a service that shows a notification that I wish that will be able to go to a specific activity of my app each time the user presses on it. Usually it would be the last one that the user has shown, but not always.
If the activity was started before, it should return to it, and if not, it should open it inside of the app's task, adding it to the activities tasks.
In addition, on some cases according to the service's logic, I wish to change the notification's intent so that it will target a different activity.
How do i do that? Is it possible without creating a new notification and dismissing the previous one? Is it also possible without creating a new task or an instance of an activity?
No it wouldn't be possible to change the Activity once you have sent the notification.
You can start an Activity on your task stack that is not a problem, check out the notification service in the tutorial here:
http://blog.blundell-apps.com/notification-for-a-user-chosen-time/
You have to set a pending intent on the notification:
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, SecondActivity.class), 0);
// Set the info for the view that shows in the notification panel.
notification.setLatestEventInfo(this, title, text, contentIntent);
You can see the pending intent takes a normal intent "new Intent(this, SecondActivity.class" so if you want specific behaviour (like bringing to the top instead of starting a new activity. Add the flags like you would normally to this intent. i.e. FLAG_ACTIVITY_REORDER_TO_FRONT (something like that)
Since platform version 11, you can build a notification using Notification.Builder. The v4 support library has an equivalent class NotificationCompat.Builder.
You can't change the Activity once you've sent the notification, but you can update the notification with a new Intent. When you create the PendingIntent, use the flag FLAG_CANCEL_CURRENT. When you send the new notification, use the ID of the existing notification when you call NotificationManager.notify().
Also, you should be careful how you start your app. The Status Bar Notifications guide tells you how to set up the back stack.
Related
We are working on notification trampolines on Android 12.
Originally our app launches an activity by a broadcast receiver.
I found out that using PendingIntent.getActivity instead of PendingIntent.getBroadcast would solve the problem.
Regarding this, I have a following concern.
When the broadcast receiver is used, i.e. when PendingIntent.getBroadcast is used, I programmed so that the broadcast receiver determines whether to launch the app.
However, I no longer use the broadcast receiver due to notification trampolines. Therefore, PendingIntent.getActivity launches the app without choice.
I would like to know if there is any way to determine whether to launch the app depending of the state of app without using the broadcast receiver.
For example;
when App is in state A:Launch the app with a push notification tap
when App is in state B:NOT launch the app with a push notification tap
sort of workaround would be to launch some dedicated Activity, which may be set as fully transparent without any enter/exit animation, noHistory flag etc. and in there you may run your checking logic - starting "real" Activity or just finish() if there is no need
I'm using a transparent activity to handle this issue. all the notification related works are handled in the transparent activity.
Intent intent = new Intent(mContext, NotificationActivity.class);
intent.putExtra("notification", parseInt(this.mActionDetail.getNotifyId()));
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
builder.setContentIntent(pendingIntent);
builder.setAutoCancel(true);
notificationManager.notify(parseInt(this.mActionDetail.getNotifyId()), builder.build());
create a transparent activity NotificationActivity.class then you can identify the application state then you can decide the action
I am working on an android project that has an alarm clock like functionality.
I schedule an intentService for each alarm instance (fires at 9pm, for example.), this intentService builds the notification and displays it. The notification includes a fullScreenIntent, which works as expected and launches the activity. I use the following code to do this:
alarmActivityIntent = new Intent(this, AlarmActivity.class);
PendingIntent alarmActivityPendingIntent = PendingIntent.getActivity(this, alertSchedule.getIntentId(), alarmActivityIntent, PendingIntent.FLAG_CANCEL_CURRENT);
mBuilder.setFullScreenIntent(alarmActivityPendingIntent, true);
Notification mNotification = mBuilder.build();
mNotificationManager.notify(alertSchedule.getIntentId(), mNotification);
This works as expected when only one alarm is set to fire at a specific time, however if two alarms are set to fire at a specific time the behavior changes.
I want the first fullScreenIntent to start its activity, then when that activity finishes, show the next one. I believe I want to build up a task stack, and push these alarm intents onto it. However this is all new to me.
Is it possible to group these notifications?
If you are using the full screen Intent feature, you don't need to use an IntentService or a Notification at all. Just have the AlarmManager start your Activity when it fires.
You don't want to build a task stack, that is all too complicated. If you have multiple alarms that can fire at the same time, you can have the AlarmManager start the same Activity each time. Set the launch mode of this Activity to singleTop (in the manifest), so that if the Activity is already showing when the alarm fires, it will not create another instance of the Activity on top of the existing one, but instead will deliver the Intent by calling onNewIntent() on the existing instance of the Activity. In onNewIntent() you can save the data (extras) of the Intent in a queue that will be processed when the user finishes the currently shown Activity. To do that, just override onBackPressed() so that when the user presses the BACK button to finish the current Activity, you can check if there are any additional alarms waiting in the queue. If there are none, you can just call super.onBackPressed() to finish the Activity. If there is anything in the queue, you can remove the first element in the queue and display that in your Activity. Keep doing that until the queue is empty.
According to the specification, .setDeleteIntent(pendingIntent) is associated to both actions (CLEAR all events from notification bar and user action like swiping).
My requirements are that when the user touches the notification that appears on the notification bar, he must be forwarded to the NotificationsList.class. This is done with my pendingInent:
PendingIntent sendPendingIntent = PendingIntent.getActivity(context, reminderId, new Intent(context, NotificationsList.class), PendingIntent.FLAG_UPDATE_CURRENT);
However, on clicking the CLEAR button, the user must not be navigated to the application at all. With the .setDeleteIntent(pendingIndent) I cannot fulfill the 2nd requirement. The user is still navigated to NotificationsList.class.
Is there a way to programmatically distinguish the CLEAR all notifications events fired from the CLEAR button from user actions like touch or swipe on the specific notification on the notification bar?
What you're describing is very obtuse behavior. You need only set the pending intent to your notification and when it is clicked, the intent that is backing it will be executed.
If your code is navigating the user back to the app when the notification is cleared, then you already have a problem with your design. If the user clears your notification you should NOT be trying to navigate them back. Hence the setDeleteIntent() should NOT be associated with starting any activity.
Note that the intent that is backed when you click the notification (setContentIntent()) and clear (setDeleteIntent()) the notification are basically two PendingIntents, they should not be the same, which is what your problem is describing.
You cannot distinguish the two events. As the documentation says:
Notifications remain visible until one of the following happens:
The user dismisses the notification either individually or by using "Clear All" (if the notification can be cleared).
The user clicks the notification, and you called setAutoCancel() when you created the notification.
You call cancel() for a specific notification ID. This method also deletes ongoing notifications.
You call cancelAll(), which removes all of the notifications you previously issued.
So there are basically three different events in the view of a programmer:
You dismisses the notification
The user clicks on the notification
The user dismisses the notification (either by swiping or clearing it)
The first event is fired by yourself by calling cancelAll() or cancel().
You can handle the second like (which you wanna do I think):
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
//....
.setContentIntent(sendPendingIntent);
And you can handle the third event like (as you have described above):
builder.setDeleteIntent(pendingIndent)
I don't recommend to start an activity after the user dismisses your notification, because the user won't expect it and it will be a bad user experience.
I hope I could help.
According to the design guidelines, the user can expect to interact with your notification using higher-level gestures like click, swipe, and pinch zoom. Responding instantly to a lower level event like touch would short circuit these gestures, so your requirements would violate the design guidelines and you should not implement it.
If the requirements are changed so that the user is forwarded when they click on the notification, there is no need to distinguish between swiping and clearing, which is impossible in any case.
So your issue should be resolved by changing one word in the requirements: touch --> click.
I googled deleteIntent to find some info for a problem which led me here.
English is my second language. Sorry for some misuse of words in advance. I'm an android newbie, just downvote the answer if it sucks :)
For your last question, just as #x-code and #bendaf said, it's impossible to
distinguish swiping and clearing.
I am following the codelabs about notifications and encountered the same question(The description in your title). So I decided to offer more detail about how to use .setDeleteIntent in your case. Maybe you had done that.
In your case, the wrapped intent is for starting an activity, so do the pendingIntent.
PendingIntent sendPendingIntent = PendingIntent.getActivity(context, reminderId, new Intent(context, NotificationsList.class), PendingIntent.FLAG_UPDATE_CURRENT);
But for performing a broadcast, e.g. doing some stuff when the notification is cleared, use:
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, NOTIFICATION_ID, new Intent(yourCustomActionString), PendingIntent.FLAG_UPDATE_CURRENT);
builder.setDeleteIntent(pendingIntent); // the pendingIntent will be sent when the notification is cleared
Then we need a custom broadcast receiver receive that custom action contained in the Intent object, in your case, this action relates to the clearing:
// Inside onCreate, register the broadcast receiver;
registerReceiver(new MyReceiver(), new IntentFilter(yourCustomActionString));
.
.
// Create an inner class
public class MyReceiver extends BroadcastReceiver {
public NotificationReceiver() {}
#Override
public void onReceive(Context context, Intent intent) {
// code inside will be executed when pendingIntent is sent
Log("taG", "Notification is cleared"); // a message will be logged if the notification is cleared
// for more than one action, using switch...case to decide
}
}
There are a lot of questions/answers about how to start an application from within your application in Android. But those solutions do not produce the same flow as if an icon was tapped in Android launcher.
For example, I do this (this is used with notifications):
intent = context.getPackageManager().getLaunchIntentForPackage("com.test.startup");
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pendingIntent = PendingIntent.getActivity(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
Then when I tap on notification the app is started, however, it is started somewhat differently than when I tap the icon in the App drawer. Specifically: with this approach my main Activity is always created (i.e. onCreate() then onResume() is called). However, if application was already started and then put in background, then starting it from Launcher will only cause onResume() of currently shown activity to be called (not onCreate() on the main one). Is there a way to trigger the same resume flow programmatically from within my app?
To summarize the task: when user taps on notification I need my app to be either started (if it's not already), or brought to the foreground in its current state (if it's in background) and have some data passed to it. The app will then take care of handling/rendering that data.
Your app is behaving the way it supposed to. Even if you try the launch the app from App drawer it will call the same callback. You have to understand the lifecycle. As your activity is in the background onCreate will not get called. But for the handling the data from the notification intent you should utilize callback method OnNewIntent() in activity. You should override this method and extract the data the from the new intent and should update UI. After onNewIntent onresume will be called.
I hope this solves your problem.
Here is my onPause code which works the way you expected i.e when user clicks on the notification it doesnt call onCreate again:
notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
intent = new Intent(getApplicationContext(), PlayerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pIntent = PendingIntent.getActivity(getBaseContext(), 0, intent,0);
NotificationCompat.Builder noti =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_media_play)
.setContentTitle("Nepali Music And more")
.setContentText("Playing");
noti.setContentIntent(pIntent);
noti.setAutoCancel(true);
noti.setOngoing(true);
Notification notification = noti.getNotification();
notificationManager.notify(1, notification);
Focus mainly on the intent flags
You want to use the intent flags Intent.FLAG_ACTIVITY_CLEAR_TOP to find your activity and clear the stack above it. You also need the Intent.FLAG_ACTIVITY_SINGLE_TOP flag to prevent your activity from being recreated (to resume).
The Intent.FLAG_ACTIVITY_SINGLE_TOP is necessary since by default, the launch mode is "standard" which lets you create multiple instances of your activity. If you were to set your launch mode to SingleTop, then this flag own't be necessary
I have created a simple application having a button. Clicking it triggers a notification, and clicking on the notification launches a new instance of the same application. However, I wanted that clicking on the notification should bring me back to the application instance from which the notification was triggered. For this I consulted the Android docs for the FLAG_ACTIVITY_NEW_TASK flag-
When using this flag, if a task is already running for the activity you are now starting, then a new activity will not be started; instead, the current task will simply be brought to the front of the screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK for a flag to disable this behavior.
Based on this when creating the intent for passing to the PendingIntent, i set this flag. However, clicking on the notification still launches a new instance of the application.
What am I doing wrong ?
Remember that when you click the Notification it is from that Context that the intent is being launched. That context doesn't have the Activity on it's task (infact, it will be a blank task).
What this results in is two version of the same Activity (although still only one instance of you Application) running. Each Activity is running a different Task.
If you don't need duplicate Activities of the same type in any of your stacks you could use the answer here:
https://stackoverflow.com/a/2327027/726954
Otherwise, there are many ways to "fix" this problem, including singleton variables and Application Context methods that keeps track of which Activities are in a Running state.
You may need to search and refine your question for those.
A Task in Android is a separate User workflow. If you mange to see the Homescreen sometime, that usually means you start a new one.
Remove the flag and it should work. if it does not, try using Single top.
Try the below code:
Intent resultIntent = new Intent(context, YourActivity.class);
resultIntent.setAction(Intent.ACTION_MAIN) resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
resultIntent, 0);
.setContentIntent(pendingIntent)