Callback on push notification open Android - android

I know we can launch activities on notification open like this:
NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
Intent targetIntent = new Intent(this, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, targetIntent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
//...
But what if, instead of lunch an activity, I want to run a callback, like some onPushOpen, so to choose there how to handle the notification click?

You can declare an activity in your manifest, call it NotificationClickHandlerActivity, which you will set as the target for the pending intent of your notification instead of MainActivity.
In the onCreate method of this new activity, just call getIntent, based on the information in the intent (action, categories, extras) determine what needs to be opened next, start the new target activity which you determined or do some other action, then call finish from inside onCreate and return immediately.
If an activity calls finish in onCreate like this handler, it will never be shown to the user, so he will not be aware that it even exists, all he'll see is the other activity which you potentially launch from it.
Also, keep in mind that you should not do long running operations in the onCreate method of this handler activity as they will be executed on the UI thread and will block your app from responding to user input. If you have to do something longer running, put it inside an async task subclass (either STATIC nested class of the handler activity, or completely separate class), then start an instance of this task from the onCreate method, and call finish right after the task is started. The task will continue running in the background. If the task needs a context to do whatever it has to do, make sure you pass it an application context by calling getApplicationContext, and don't pass the activity itself otherwise you will have memory leaks.

You could use EventBus to achive that. Just post some event like onPushOpen() and catch it anywhere you want. Make smth like:
Intent targetIntent = new Intent(this, NaughtyActivity.class);
And make NaughtyActivity use no layout. Use it just to post the event and finish.

Related

Multiple fullscreen android notifications at the same time not stacking

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.

Android notification starting new instance of activity no matter what the launchMode and/or flags are set to

I've been doing a lot of research on this and I've hit a wall. I've read what seems like all of the material on this topic (inluding this, this, and this as well as the docs) and nothing is helping me in what I want to do.
Basically, in the case that the app is open on the user's phone, I just want a notification to redirect the user to the already-existing Activity in my app. I use a pattern like this:
private PendingIntent buildPendingIntentForNotification(String type) {
Intent resultIntent = new Intent(this, MainActivity.class);
resultIntent.setAction(Intent.ACTION_VIEW);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);// have tried without this and with Intent.FLAG_ACTIVITY_CLEAR_TOP
resultIntent.putExtra(CommonUtils.NOTIFICATION_TYPE, type);
PendingIntent resultPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return resultPendingIntent;
}
I've also tried declaring all 4 types of launchModes in the Android Manifest within the activity tag. No matter what I do, clicking on the notification (even if the app is the active foreground app on the screen and MainActivity is the active activity) always seems to restart the activity from onCreate. From what I can see during debugging, onDestroy is not called at any time between getting the notification and clicking on it. However, after the notification is clicked, onCreate is called and THEN onDestroy is called even though my activity is not being destroyed, which is very strange. I'm hoping someone can help me make sense of this because all of the launchMode and Intent.setFlags suggestions are not working for me. Thanks a lot!
Just for info's sake, here's the code I used to fix my problem (with credit to David's solution):
private PendingIntent buildPendingIntentForNotification(String type) {
Intent resultIntent = new Intent(this, MainActivity.class);
//resultIntent.setAction(Intent.ACTION_VIEW);
resultIntent.setAction(Intent.ACTION_MAIN);
resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resultIntent.putExtra(CommonUtils.NOTIFICATION_TYPE, type);
PendingIntent resultPendingIntent = PendingIntent.getActivity(getApplicationContext(), 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return resultPendingIntent;
}
There are 2 ways of doing this:
Notification to restore a task rather than a specific activity?
Resume application and stack from notification
However, you may also be seeing this nasty Android bug.
You cannot control and cannot be sure about the state of your activities on the Android, meaning (in your context) you can only start an activity that has been paused not one that has been destroyed. The OS decides which activities will keep paused and which will destroy and you have no control over that. When your activity leaves the foreground successive callbacks are being invoked at undefined times and to the OS's
What you can simply do is to save your activity's instance state in the onPause() method (which we know for sure that will be called as soon as the activity leaves the foreground) and the on onCreate() you can restore the activity with the data as it previously was.
Note: If you have for example Activity A and activity B and you start activity B from within activity A, then on activity B's onCreate() method you can getIntent().getExtras()

how can I tell if an Activity is reached through PendingIntent

Is there a way to tell if I reached some Activity through regular flow of the app or whether I reached this (deep) Activity from a Notification via PendingIntent?
I need to perform some operations when the application starts and if I got to this Activity via notification I need to make sure these operations are made.
You can put an extra on your PendingIntent and than when the activity starts check for it using getIntent() ("Return the intent that started this activity."). Doing so you can getExtras() and check how the activity was started.

How to start an app the same way Android Launcher does (resume)

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

Android - use of FLAG_ACTIVITY_NEW_TASK

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)

Categories

Resources