38

I have a foreground service that keeps a connection open with the server as long as the user is logged into the application. This is so that the connection is kept alive and can receive messages directly from the server even when the application has been sent into the background by the user pressing Home.

The application has a number of Activities, any of which could be the active one when it is sent into the background.

I would like to allow the user to click on the notification to restore the current Activity. I understand how to restore a particular activity, but wondered if there is a way to restore the last Activity that the user was on? Of course I could keep track of the the last one, and then call that from the Notification callback, but thought there might be a way at a task level?

Thanks for any advice you can offer.

Brad
  • 507
  • 1
  • 4
  • 12

6 Answers6

78

What you need is just a simple Activity that does nothing. Here is an example:

public class NotificationActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Now finish, which will drop the user in to the activity that was at the top
        //  of the task stack
        finish();
    }
}

Set up your notification to start this activity. Make sure that in the manifest the task affinity of this activity is the same as the task affinity of the other activities in your application (by default it is, if you haven't explicitly set android:taskAffinity).

When the user selects this notification, if your application is running, then the NotificationActivity will be started on top of the topmost activity in your application's task and that task will be brought to the foreground. When the NotificationActivity finishes, it will simply return the user to the topmost activity in your application (ie: wherever the user left it when it went into the background).

This won't work if your application isn't already running. However, you have 2 options to deal with that:

  1. Make sure the notification isn't present in the notification bar when your application is not running.

  2. In the onCreate() method of the NotificationActivity, check if your application is running, and if it isn't running call startActivity() and launch your application. If you do this, be sure to set the flag Intent.FLAG_ACTIVITY_NEW_TASK when starting the application so that the root activity of the task is not NotificationActivity.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • 1
    great answer Thanks @DavidWasser – Antwan Jun 12 '15 at 09:30
  • 1
    Works well, but it's silly that this hack is the only reliable solution to this problem. No amount of intent flags made it possible to simply restore the app from the recent apps list reliably across APIs 16+. – Cord Rehn Apr 04 '17 at 17:41
  • 1
    is this still the best idea today? – Archie G. Quiñones Jan 07 '19 at 07:07
  • 1
    @ArchieG.Quiñones In general the better solution is to use a "launcher `Intent`" which you can get by calling `PackageManager.getLaunchIntentForPackage("my.package.name")`, however this solution doesn't work in all situations and it can be difficult to debug if it doesn't work. The solution in my answer should always work because it doesn't rely on special `Intent` flags or launch modes. – David Wasser Jan 07 '19 at 14:02
  • @DavidWasser I tried the answer below by Oguz Ozcan. It worked just as if putting the activity back to foreground. It is much simpler than your method. Is there any reason to prefer your solution over his? – Archie G. Quiñones Jan 08 '19 at 01:31
  • @ArchieG.Quiñones Sorry, that answer is nonsense. You should hardly ever use the special launch modes `singleTask` and `singleInstance` as this creates lots of other problems that are difficult to find if you don't know exactly what you are doing. Especially if your app has more than one `Actvity`. Do what you want, but I would definitely recommend you stay away from special launch modes. – David Wasser Jan 08 '19 at 12:02
  • As mentioned below, the only time you would ever need to change the launch mode is when you need to pass intent extras to the activity. If I don't have to do that, `Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER);` seems enough. Am I wrong? – Archie G. Quiñones Jan 09 '19 at 01:18
  • action MAIN and category LAUNCHER is not enough. You need to set the component, the package and `Intent.FLAG_ACTIVITY_NEW_TASK`. This is basically what you get by calling `PackageManager.getLaunchIntentForPackage()`. – David Wasser Jan 09 '19 at 14:51
  • 1
    BEEN SEARCHING FOR THIS. THIS IS THE SOLUTION THAT needs to be in documentation. Thanks so much! – coolcool1994 Aug 20 '20 at 09:50
  • This woks great in Android 8 but not in 10, which (for me at least) still starts a new activity rather than picking up the existing one. – quilkin Jan 22 '21 at 12:09
  • @quilkin please open a new question with your problem. This is a very complex issue and often the problem lies elsewhere. I'd be happy to look at your problem if you open a new question. – David Wasser Jan 22 '21 at 17:26
  • Thanks David. I already have a question (which led me here to your answer): (https://stackoverflow.com/questions/65837228) – quilkin Jan 22 '21 at 20:17
36

Works very well, thanks David! The following class checks if the application is already running and if not, starts it before finishing (as suggested by David in option 2).

public class NotificationActivity extends Activity 
{
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);

        // If this activity is the root activity of the task, the app is not running
        if (isTaskRoot())
        {
            // Start the app before finishing
            Intent startAppIntent = new Intent(getApplicationContext(), MainActivity.class);
            startAppIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(startAppIntent);
        }

        finish();
    }
}
Raginmari
  • 2,291
  • 23
  • 21
13

There is a simpler solution that does not require the extra activity. See this post for details. Basically, the notification starts the (possibly existing) task the same way it is started when you click the launcher icon while the app ist in the background.

Community
  • 1
  • 1
Raginmari
  • 2,291
  • 23
  • 21
4

My solution, which emulates the behaviour of the launcher (bringing up the task to the foreground):

Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setClassName(MyApplication.class.getPackage().getName(), MainActivity.class.getName());
Andrey
  • 830
  • 9
  • 12
4
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);

This works, no doubts about it but the problem is when you set your intent as ACTION_MAIN. Then you will not be able to set any bundle to the intent. I mean, your primitive data will not be received from the target activity because ACTION_MAIN can not contain any extra data.

Instead of this, you can just set your activities as singleTask and call your intent normally without setting ACTION_MAIN and receive the intent from onNewIntent() method of your target activity.

But be aware if you call, super.onNewIntent(intent); then a second instance of the activity will be created. Just don't call super method.

Oguz Ozcan
  • 1,694
  • 19
  • 26
  • Yessss! Thanks for this. `setAction` is required for some weird reason to allow different notifications to go to different classes, so your solution is required. – behelit Apr 11 '19 at 04:27
0

I combined David Wasser's and Raginmari's solution by doing that approach to the root activity of your app then it will work for both cases when your app was already started or haven't been started.

public class YourRootActivity extends Activity 
        {
            @Override
            protected void onCreate(Bundle savedInstanceState) 
        {
            super.onCreate(savedInstanceState);
    
            if (!isTaskRoot()) // checks if this root activity is at root, if not, we presented it from notification and we are resuming the app from previous open state
            {
                 val extras = intent.extras // do stuffs with extras.
                 finish();
                 return;
            }
             // OtherWise start the app as usual
        }
    }
coolcool1994
  • 3,704
  • 4
  • 39
  • 43