I have an activity which starts a service through a button press. During service initialization, if an error occurs, this error is displayed by a notification, as follows:
String tickerText = getString(R.string.init_error);
Notification notification = new Notification(
R.drawable.ic_stat_notify_msg,
tickerText, System.currentTimeMillis());
PendingIntent notificationIntent = PendingIntent.getActivity(
this, 0, new Intent(this, MyServiceActivity.class), 0);
notification.setLatestEventInfo(this,
getText(R.string.init_error_tts_title),
getText(R.string.init_error_tts_text),
notificationIntent);
NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
nm.notify(R.string.init_error, notification);
stopSelf();
When the notification appears in the status bar, I open the notifications window. Then, when I click on the notification, a new instance of MyServiceActivity is launched... Why?
Try to add this attribute on your activity tag on the manifest :
android:launchMode=["multiple" | "singleTop" |
"singleTask" | "singleInstance"]
with value singleTop or singleInstance
Activity Element Documentation
I have a music player that has the initial launcher activity, then a selection activity and then the player. The notification needs to return the user to the player. I used Christophe's idea and found that putting
android:alwaysRetainTaskState="true"
android:launchMode="singleTop"
in the manfest's activity tag of the PLAYER ACTIVITY gave me the correct behavior. Then make sure your notification invokes the PLAYER ACTIVITY class. I didn't do this at first. It invoked the app launcher class and, while this was okay for newer Androids, it left me with the extra activity problem in Android 2.3.6. Now all is good.
If my activity fix doesn't work for you, you'll have to brute-force your way through the choices found at
http://developer.android.com/guide/topics/manifest/activity-element.html#lmode
I should add that my player is stateless, just four buttons connected to a service. But if your activity needs its state set up when the notification's intent comes in, you need to override onNewIntent:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
Kudos to Christophe. This is a hard question to get the right answer for.
set a proper launch mode, or add the required intent flags.
Related
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()
I'm developing some kind of media controls for my android wear. The scenario is the following:
The WearableListenerService in the wear app is notified when the media data changes, i.e. artist/title. An ongoing notification is shown which has a full screen second page with some controls in it.
Because I don't want it to show on my smartphone, the notifications are build in the wearable app.
The second page consists of an activity baked in a display intent:
Intent pageIntent = new Intent(this, ControlActivity.class);
pageIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(
getApplicationContext(),
0,
pageIntent,
0);
Notification controlPage = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.extend(
new Notification.WearableExtender()
.setCustomSizePreset(Notification.WearableExtender.SIZE_FULL_SCREEN)
.setDisplayIntent(pendingIntent))
.build();
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.icon)
.setContentTitle(currentArtist)
.setContentText(currentTitle)
.setOngoing(true)
.extend(
new NotificationCompat.WearableExtender()
.addPage(controlPage)
.addAction(new NotificationCompat.Action(button, null, intent))
.setContentAction(0)
.setContentIcon(button)
.setBackground(BitmapFactory.decodeResource(getResources(), bg))
.setHintHideIcon(true)
)
.build();
NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification);
Now the notification is refreshed from time to time to display new data on the first notification page which forces the activity in the second page to be recreated every time the notification is updated. This results in some ugly behavior where the user can see that the activity inside the second page is recreated.
Is there any way to prevent the activity in the second page from being completely recreated (onDestroy, onCreate) and/or only update the data on the first page of the notification as it should be?
Is it the PendingIntent creation that doesn't really work here?
The Manifest entry looks as the following:
<activity
android:name=".media.ControlActivity"
android:allowEmbedded="true"
android:exported="true"
android:launchMode="singleTop"
>
I've tried all combinations of flags as well as only updating the necessary parts but nothing changes.
Help is greatly appreciated. Let me know if you need further information.
Edit: The main problem seems to be that the embedded activity (for a custom layout) in the second page of the notification is always recreated as soon as I call notify. Reusing, only updating changed values on the first page or updating nothing has no effect.
Simply broadcast the new information to your second activity and update the fields when you receive the broadcast. And make it local so the broadcast concerns only your application.
In the second activity add the BroadcastReceiver as a member attribute :
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
/*do what you're supposed to do, update your data
you get the information from the intent by calling intent.getXExtra() as usual*/
}
};
And register it in your onCreate() method :
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter("your-filter-name"));
In your notification activity, when you update the information, add this :
Intent intent = new Intent("your-filter-name");
intent.putExtra(...);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
Your second activity won't be recreated each time and the modifications should go smoothly :)
there is no method to prevent recreate the embedded activity in the notification, you can try this way in your sence.
use a action in second page, and start activity when click the action, when the notification is updated by time, the activity will not be recreated
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'm trying to add a notification icon to my application, that will function much like Waze's notification icon - when you open the status bar and tap on the "Return to MyApp" line, the application will move to foreground - with exactly the same activity stack state it had when it was moved to background.
I went over numerous SO questions, and found a lot of answers that are all very good if I know in advance which activity is going to be shown when the notification is tapped. I obviously don't know which activity it's going to be - it can be any of the app's activities.
I also tried sending a broadcast when the notification is tapped. I can the broadcast in my receiver alright, but from there I'm stuck with the exact same problem - I don't know which activity to launch - I can't find the last activity (I can only find the last task, but since my app has one task, it's no help).
you need to create a pending intent.
Like this:
Intent notifyIntent = new Intent(context, Chat.class);
PendingIntent intent =
PendingIntent.getActivity(Chat.this, 0, notifyIntent, SIMPLE_NOTFICATION_ID);
notifyDetails.setLatestEventInfo(context, contentTitle, contentText, intent);
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
mNotificationManager.notify(SIMPLE_NOTFICATION_ID, notifyDetails);
please see this link
sample project
related
In Android, I defined an activity ExampleActivity.
When my application was launched, an instance of this A-Activity was created, say it is A.
When user clicked a button in A, another instance of B-Activity, B was created. Now the task stack is B-A, with B at the top. Then, user clicked a button on B, another instance of C-Activity, and C was created. Now the task stack is C-B-A, with C at the top.
Now, when user click a button on C, I want the application to bring A to the foreground, i.e. make A to be at the top of task stack, A-C-B.
How can I write the code to make it happen?
You can try this FLAG_ACTIVITY_REORDER_TO_FRONT (the document describes exactly what you want to)
The best way I found to do this was to use the same intent as the Android home screen uses - the app Launcher.
For example:
Intent i = new Intent(this, MyMainActivity.class);
i.setAction(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(i);
This way, whatever activity in my package was most recently used by the user is brought back to the front again. I found this useful in using my service's PendingIntent to get the user back to my app.
Here is a code-example of how you can do it:
Intent intent = getIntent(getApplicationContext(), A.class)
This will make sure that you only have one instance of an activity on the stack.
private static Intent getIntent(Context context, Class<?> cls) {
Intent intent = new Intent(context, cls);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
return intent;
}
FLAG_ACTIVITY_REORDER_TO_FRONT:
If set in an Intent passed to Context.startActivity(), this flag will cause the launched activity to be brought to the front of its task's history stack if it is already running.
Intent i = new Intent(context, AActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(i);
I think a combination of Intent flags should do the trick. In particular, Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_NEW_TASK.
Add these flags to your intent before calling startActvity.
i.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
Note Your homeactivity launchmode should be single_task
In general I think this method of activity management is not recommended. The problem with reactivating an activity two Steps down in The Stack is that this activity has likely been killed. My advice into remember the state of your activities and launch them with startActivity ()
I'm sure you've Seen this page but for your convenience this link
If you want to bring an activity to the top of the stack when clicking on a Notification then you may need to do the following to make the FLAG_ACTIVITY_REORDER_TO_FRONT work:
The solution for me for this was to make a broadcast receiver that listens to broadcast actions that the notification triggers. So basically:
Notification triggers a broadcast action with an extra the name of the activity to launch.
Broadcast receiver catches this when the notification is clicked, then creates an intent to launch that activity using the FLAG_ACTIVITY_REORDER_TO_FRONT flag
Activity is brought to the top of activity stack, no duplicates.
If you use adb am cmd for start,you should use cmd like below
adb shell am start -n com.xxx.xxx/com.xxx.Activity --activity-reorder-to-front
adb intent argument
adb intend -f argument descript
if you are using the "Google Cloud Message" to receive push notifications with "PendingIntent" class, the following code displays the notification in the action bar only.
Clicking the notification no activity will be created, the last active activity is restored retaining current state without problems.
Intent notificationIntent = new Intent(this, ActBase.class);
**notificationIntent.setAction(Intent.ACTION_MAIN);
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);**
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("Localtaxi")
.setVibrate(vibrate)
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setContentText(msg);
mBuilder.setContentIntent(contentIntent);
NotificationManager mNotificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
Ciao!