David Berry
David Berry

Reputation: 41226

How to keep back stack with notification

I'm trying to present a notification which is handled by a special activity which is not in the normal application flow, and trying to get the back stack handling "correct," meaning:

  1. if a notification is handled while the application is running, the notification activity should appear on the current stack, back from the notification should leave us where we were in the application. Note that this may mean the application is made open.
  2. if a notification is handled while the application isn't running, the notification activity should appear on the main (initial) activity of the application.

So far, the code I'm using to present the notification is:

/**
 * Show (or update) a notification for the current message set.
 * 
 * @param showNotification true to use a high priority notification which will be immediately
 *                         displayed (as opposed to just added to the status bar)
 */
private void createOrUpdateNotification(boolean showNotification) {
    Message oldest = messages.get(0);
    Message newest = messages.get(messages.size() - 1);

    // Create the notification
    NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
            // Set notification data and appearance
            .setContentTitle(title)
            .setContentText(newest.message)
            .setSmallIcon(smallIcon)
            .setWhen(newest.when)
            .setColor(ContextCompat.getColor(context, R.color.primary_color))

            // Set notification options
            .setAutoCancel(true)
            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
            .setPriority(showNotification ? NotificationCompat.PRIORITY_HIGH : NotificationCompat.PRIORITY_LOW)
            .setDefaults(NotificationCompat.DEFAULT_VIBRATE)
            .setOnlyAlertOnce(!showNotification);

    // Set up the action if the first (oldest) message has an intent builder
    if(oldest.intent != null) {
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context.getApplicationContext());
        stackBuilder.addNextIntent(oldest.intent);
        builder.setContentIntent(stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
    }

    NotificationManagerCompat.from(context).notify(notificationId, builder.build());

    Log.i("notification created");
}

For clarification, Message.intent is a single intent, configured to open the notification handling activity.

My problem is that if the application is currently running and open when the notification is opened, the application is closed and the notification presented on an empty stack and the application's back stack is cleared out.

My understanding is that the desired behavior should be automatic if the content intent is a pending intent containing a single intent, which is the case here.

What am I missing?

Upvotes: 4

Views: 2632

Answers (3)

David Berry
David Berry

Reputation: 41226

To expand a little bit on @leco's answer, my final solution had two parts.

The first is building the Notification as he recommended, using a PendingIntent directly instead of trying to use a TaskStackBuilder:

builder.setContentIntent(
  PendingIntent.getActivity(
    context, 
    0, 
    oldest.intent, 
    PendingIntent.FLAG_UPDATE_CURRENT
));

This got me the correct behavior if the application was running, but not currently open (ie., up until the user hit back all the way out of the application)

To get the "correct" behavior at that point, I modified my Notification handling Activity by overriding finish():

public void finish() {
    if(isTaskRoot()) {
        // If we're the task root, probably because we were launched from a notification with the
        //  application closed, we should just open the default activity
        try {
            String homeClassName = getPackageManager().queryIntentActivities(
                    new Intent(Intent.ACTION_MAIN).setPackage(getApplicationContext().getPackageName()),
                    0
            ).get(0).activityInfo.name;

            Class homeClass = Class.forName(homeClassName);

            getApplicationContext().startActivity(new Intent(getApplicationContext(), homeClass).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
        }
        catch(Exception exception) {
            Log.w(String.format("Can't find the MAIN activity for package %s", getApplicationContext().getPackageName()));
        }
    }
    super.finish();
}

The gyrations with queryIntentActivities etc are due to the fact that I'm actually developing a drop-in component for applications, so I don't actually know what their root/home/launch activity actually is. I tried a simpler approach of just building a HOME intent:

startActivity(new Intent(Intent.ACTION_MAIN).setPackage(getApplicationContext().getPackageName())

but for some reason startActivity was throwing an exception:

android.content.ActivityNotFoundException: No Activity found to handle Intent

even though the queryIntentActivities approach demonstrates that there is an appropriate Activity for the Intent.

Upvotes: 1

Joe
Joe

Reputation: 81

Create a Queue of Bundle and keep adding and removing Bundle Objects from the queue as per your requirement.

/**
     * Queue that holds notifications messages
     */
    private Queue<Bundle> mNotificationQueue = new LinkedList<Bundle>();

/**
* Method returning the singleton queue maintained in a static class  
*/
 public Queue<Bundle> getNotificationQueue() {
        return mNotificationQueue;
    }

Now when the Notification is received add the notification message to the queue

// Fetching the message from intent and adding in bundle to add in the queue

Bundle notificationMsgBundle = intent.getExtras();

notificationMsgBundle.getString(MyConstants.KEY_CHANNEL) + " :: " + notificationMsgBundle.getString(MyConstants.KEY_DATA));

DataManager.getInstance().getmNotificationQueue().add(notificationMsgBundle)

Now this static queue is maintained throughout the application and you can remove or add messages from the queue

// removing the node message in the queue as per requirement
 DataManager.getInstance().getmNotificationQueue().remove();

Upvotes: 0

Luis
Luis

Reputation: 3571

TaskStackBuilder.create will launch a fresh task stack.

Set the content intent like this instead:

builder.setContentIntent(
  PendingIntent.getActivity(
    context, 
    0, 
    oldest.intent, 
    PendingIntent.FLAG_UPDATE_CURRENT
));

Upvotes: 1

Related Questions