How to make notification intent resume rather than making a new intent?

52,103

Solution 1

The idea is that people can navigate away from this activity and quickly access it again from any screen they want by pulling down the drop down menu and selecting it.

Please make this optional.

However, when the notification is pressed it starts a new instance of the activity.

That will happen by default.

What would i have to change to make it see if the activity has not already been destroyed and i can just call that instance back(resume it) and therefore not needing to load it again and won't need to add another activity to my stack.

Get rid of FLAG_ACTIVITY_NEW_TASK. Add notifyIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); -- see this sample project.

Context context = getApplicationContext();

Please don't do this. Just use your Activity as a Context.

Solution 2

This is my method to show notifications. I hope it helps you.

private static void generateNotification(Context context, String message){
    Intent notificationIntent = new Intent(context, YOUR_ACTIVITY.class);
    notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    PendingIntent intent = PendingIntent.getActivity(context, 0, notificationIntent, 0);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context)
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentTitle(context.getString(R.string.app_name))
        .setContentIntent(intent)
        .setPriority(PRIORITY_HIGH) //private static final PRIORITY_HIGH = 5;
        .setContentText(message)
        .setAutoCancel(true)
        .setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE | Notification.DEFAULT_LIGHTS);
    NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.notify(0, mBuilder.build());
}

Solution 3

Just in case someone else has the same difficulty I had....

I tried all the code above without success - the notification item kept launching a completely new instance of my app even though one was already running. (I only want one instance to be available at any one time.) Finally noticed this other thread (https://stackoverflow.com/a/20724199/4307281), which had one simple additional entry needed in the manifest:

android:launchMode="singleInstance"

I placed that in the manifest under the main activity section of my application and then the notification item reused the already-in-progress instance.

Solution 4

Non of the solutions worked for me so I found my own way. All my activities are extended from a BaseActivity. I followed a simple series of steps to make my push notification click resume the application:

Step 1 : Constant to record the Current Activity:

public static Context currentContext;

Step 2 : in onCreate() or onResume() of activities extended by BaseActivity

currentContext = this;

Step 3 : Intent when creating the notification:

Class activity = BaseActivity.commonContext.getClass();
Intent intent = new Intent(this, activity);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

//FLAG_UPDATE_CURRENT is important
PendingIntent pendingIntent = PendingIntent.getActivity(this, (int)System.currentTimeMillis(), intent, PendingIntent.FLAG_UPDATE_CURRENT);

Solution 5

I found that none of the answers worked for a multi task application. It seems that, understandably, Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_SINGLE_TOP do not behave exactly the same in the case of multiple tasks. In my application, there are multiple task root activities. At some point, the user will be shown a notification that there is work to be done in one of these tasks. Unlike the OP, I only need to return to the top of one of these tasks, not to a specific activity, which may be buried in one of these tasks. I think that this is similar enough to the asked question that it may be of use to someone else.

My solution consists of a static task id to keep track of the current task, a broadcast receiver to handle pending intents, a new uses-permission tag, and some simple use of the ActivityManager.

private static int taskID;

public static void setResumeTask(int tID)
{
    Log.e("TaskReturn", "Setting Task ID: " + tID);
    taskID = tID;
}

public static PendingIntent getResumeTask(Context appContext)
{
    return PendingIntent.getBroadcast(appContext, 0, new Intent(appContext, TaskReturn.class).putExtra(TaskReturn.EXTRA_TASK_ID, taskID), PendingIntent.FLAG_UPDATE_CURRENT);
}

public void onReceive(Context context, Intent intent)
{
    Log.e("TaskReturn", "Task Return!");
    if (intent.hasExtra(EXTRA_TASK_ID))
    {
        int taskToStart = intent.getIntExtra(EXTRA_TASK_ID, -1);
        Log.e("TaskReturn", "Returning To Task: " + taskToStart);
        ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.AppTask> allAppTasks = activityManager.getAppTasks();

        for (ActivityManager.AppTask task : allAppTasks)
        {
            Log.e("TaskReturn", "AllTasks: " + task.getTaskInfo().id + " " + task.getTaskInfo().affiliatedTaskId + " " + task.getTaskInfo().persistentId);
        }

        activityManager.moveTaskToFront(taskToStart, ActivityManager.MOVE_TASK_WITH_HOME);
    }
    else
    {
        Log.e("TaskReturn", "WHERE IS MY EXTRA!?!?!");
        //Boo...
    }
}

<uses-permission android:name="android.permission.REORDER_TASKS"/>

...

<receiver android:name=".TaskReturn"/>

Basically the idea is that you mark the result of a call to Activity.getTaskId() to the state. Later, when creating a notification to resume to that activity's task, grab the task ID and throw it into an extra. Then, set the pending intent to point to a broadcast receiver that pulls out the task id and fires up that task.

Looks like this requires API 21 with the calls I used, and I suspect it might need some additional error checks if the task has already been quit, but it serves my purposes.

Share:
52,103
brybam
Author by

brybam

Brinkbit.com

Updated on March 15, 2020

Comments

  • brybam
    brybam about 4 years

    What i have here is a simple webview activity that when loaded it auto displays an ongoing notification. The idea is that people can navigate away from this activity and quickly access it again from any screen they want by pulling down the drop down menu and selecting it. Then when they want they can just close the notification by hitting the menu button and hitting exit and then the notification clears. This all works fine. However, when the notification is pressed it starts a new instance of the activity. What would i have to change to make it see if the activity has not already been destroyed and i can just call that instance back(resume it) and therefore not needing to load it again and won't need to add another activity to my stack. Any ideas? Any help would be greatly appreciated.

    package com.my.app;
    
    import com.flurry.android.FlurryAgent;
    
    import android.app.Activity;
    import android.app.Notification;
    import android.app.NotificationManager;
    import android.app.PendingIntent;
    import android.app.ProgressDialog;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.KeyEvent; 
    import android.view.Menu;
    import android.view.MenuInflater;
    import android.view.MenuItem;
    import android.webkit.CookieSyncManager;
    import android.webkit.WebView;
    import android.webkit.WebViewClient;
    import android.widget.Toast;
    
    public class Chat extends Activity { 
        private ProgressDialog progressBar;
        public WebView webview;
        private static final String TAG = "Main";
    
        private NotificationManager mNotificationManager;
        private int SIMPLE_NOTFICATION_ID;
    
        @Override
        public void onStart() {
           super.onStart();
           CookieSyncManager.getInstance().sync();
           FlurryAgent.onStartSession(this, "H9QGMRC46IPXB43GYWU1");
        }
    
        public void onCreate(Bundle savedInstanceState) { 
            super.onCreate(savedInstanceState);
            setContentView(R.layout.chat);
    
            mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    
            final Notification notifyDetails = new Notification(R.drawable.chat_notification,"Chat Started",System.currentTimeMillis());
    
            notifyDetails.flags |= Notification.FLAG_ONGOING_EVENT;
    
            Context context = getApplicationContext();
    
            CharSequence contentTitle = "Chat";
            CharSequence contentText = "Press to return to chat";
    
            Intent notifyIntent = new Intent(context, Chat.class);
    
            PendingIntent intent =
            PendingIntent.getActivity(Chat.this, 0,
            notifyIntent, android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
            notifyDetails.setLatestEventInfo(context, contentTitle, contentText, intent);
    
            mNotificationManager.notify(SIMPLE_NOTFICATION_ID, notifyDetails);
    
            CookieSyncManager.createInstance(this);
            CookieSyncManager.getInstance().startSync();
            webview = (WebView) findViewById(R.id.webviewchat);
            webview.setWebViewClient(new chatClient());
            webview.getSettings().setJavaScriptEnabled(true);
            webview.getSettings().setPluginsEnabled(true);
            webview.loadUrl("http://google.com");
    
            progressBar = ProgressDialog.show(Chat.this, "", "Loading Chat...");  
        }
    
        private class chatClient extends WebViewClient { 
            @Override 
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                Log.i(TAG, "Processing webview url click...");
                view.loadUrl(url);
                return true;
            }
    
            public void onPageFinished(WebView view, String url) {
                Log.i(TAG, "Finished loading URL: " +url);
                if (progressBar.isShowing()) {
                    progressBar.dismiss();
                }
            }
        }
    
        public boolean onKeyDown(int keyCode, KeyEvent event) { 
            if ((keyCode == KeyEvent.KEYCODE_BACK) && webview.canGoBack()) { 
                webview.goBack(); 
                return true; 
            }
            return super.onKeyDown(keyCode, event); 
        }
    
        @Override
        public boolean onCreateOptionsMenu (Menu menu) {
            super.onCreateOptionsMenu(menu);
            MenuInflater inflater = getMenuInflater();
            inflater.inflate(R.menu.chatmenu, menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected (MenuItem item) {
            switch (item.getItemId()) {
                case R.id.home:
                    Intent a = new Intent(this, Home.class);
                    startActivity(a);
                    return true;
                case R.id.closechat:
                    mNotificationManager.cancel(SIMPLE_NOTFICATION_ID);
                    Intent v = new Intent(this, Home.class);
                    startActivity(v);
                    return true;
            }
            return false;
        }
    
        public void onStop() {
           super.onStop();
           CookieSyncManager.getInstance().sync();
           FlurryAgent.onEndSession(this);
        }
    }
    

    @Commonsware

    Just to be sure i have it correct, is this what you were suggesting?

    I was a little worried about this line,

    PendingIntent.getActivity(Chat.this, 0, notifyIntent, SIMPLE_NOTFICATION_ID);
    
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.chat);
    
        mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    
        final Notification notifyDetails = new Notification(R.drawable.chat_notification,"Chat Started",System.currentTimeMillis());
    
        notifyDetails.flags |= Notification.FLAG_ONGOING_EVENT;
    
    
        Context context = getApplicationContext();
    
        CharSequence contentTitle = "Chat";
        CharSequence contentText = "Press to return to chat";
    
        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);
    
    
        CookieSyncManager.createInstance(this);
        CookieSyncManager.getInstance().startSync();
        webview = (WebView) findViewById(R.id.webviewchat);
        webview.setWebViewClient(new chatClient());
        webview.getSettings().setJavaScriptEnabled(true);
        webview.getSettings().setPluginsEnabled(true);
        webview.loadUrl("http://google.com");
    
        progressBar = ProgressDialog.show(Chat.this, "", "Loading Chat...");        
    }
    
  • brybam
    brybam almost 14 years
    thaks a lot for taking a look at this! when you get a chance would you mind taking a look at the edit up top?
  • Aldo Reyes
    Aldo Reyes over 11 years
    Take a look a the sample project @commonsware suggest, it helped me a lot to get a good understanding of how it should be done
  • Marthyn Olthof
    Marthyn Olthof over 11 years
    This worked for me too but the Notification doesn't go away now, any tips on that?
  • CommonsWare
    CommonsWare over 11 years
    @MarthynOlthof: FLAG_AUTO_CLEAR on the Notification.
  • yeahman
    yeahman almost 11 years
    how to resume current instance of current task from notificaition.. i have a problem with my sharing app.. when i share photos via the gallery app, it opens my shareactivity.. i created a notification using the code above but instead of resuming the current instance of the current task, it opens the instance found in my app's task's stack instead (or create a new instance if it does not exist)
  • Renetik
    Renetik about 10 years
    Why not use ApplicationContext ? What if I am creating notification from BroadcastReceiver and I don't have active activity...
  • CommonsWare
    CommonsWare about 10 years
    @ReneDohan: Use the Context supplied to onReceive().
  • nAkhmedov
    nAkhmedov over 9 years
    I`ve started my notification by calling startForeground(SERVICE_NOTE_ID, note); But it doesn't work, it opens 'Chat' class not my resuming activity. here i created a question like stackoverflow.com/questions/26904705/…
  • Pierre Maoui
    Pierre Maoui about 9 years
    This worked for me. If you build for example a GPS activity, this would be the correct way to do.
  • Alexeev Valeriy
    Alexeev Valeriy almost 9 years
    This variant creates a new instance upon clicking on notification. Though author asked about resuming activity, not creating a new one. Strange that this answer gathered so many points.
  • dhakim
    dhakim about 7 years
    This solution appears to work correctly for an application with a single task. It does not appear to work correctly if there are multiple launcher activities with different task affinities. In that case, the activity to resume appears to be recreated in a new task, rather than resuming from the instance at the top of a current task. Is there an extension to further specify which task to resume, such that the target activity is not recreated in this case?
  • CommonsWare
    CommonsWare about 7 years
    @dhakim: You cannot specify "which task to resume" directly. You would have to try to mess around with Intent flags and manifest attributes to get the effects that you want. I have not attempted anything akin to what you describe, so I do not have a recipe for you, sorry.
  • Mijo
    Mijo almost 7 years
    This worked for me, and is the simplest solution, and the most straight forward. Thanks for sharing.
  • FRANCIS FERNANDO
    FRANCIS FERNANDO over 3 years
    great solution i couldn't find any errors after changing the manifest