Up navigation broken on JellyBean?

First things first, if you are targeting devices as high as API 17 (Android 4.2), set the targetSdkVersion to 17 in your manifest. This doesn’t break support for old devices, it just makes things work properly for newer devices. Of course, that doesn’t fix your problem — it’s just good to do it.

What Should You Use?

I assume you are basing your code off of the Ancestral Navigation example on this page. You’ve used the second example:

Intent upIntent = new Intent(this, MyParentActivity.class);
if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
    // This activity is not part of the application's task, so create a new task
    // with a synthesized back stack.
    TaskStackBuilder.from(this)
        .addNextIntent(new Intent(this, MyGreatGrandParentActivity.class))
        .addNextIntent(new Intent(this, MyGrandParentActivity.class))
        .addNextIntent(upIntent)
        .startActivities();
    finish();
 } else {
     // This activity is part of the application's task, so simply
     // navigate up to the hierarchical parent activity.
     NavUtils.navigateUpTo(this, upIntent);
 }

For the behavior you want, however, you would need to replace NavUtils.navigateUpTo with:

upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(upIntent);
finish();

Why does it work on ICS and Earlier?

In general, the support library is trying to approximate the behavior introduced in later APIs. In the case of NavUtils, the support library is trying to approximate the bahavior introduced in API 16 (a.k.a. Android 4.1). For pre-API 16 platforms, NavUtils uses:

@Override
public void navigateUpTo(Activity activity, Intent upIntent) {
    upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    activity.startActivity(upIntent);
    activity.finish();
}

This looks through the back stack for an instance of the activity specified in upInent. If it finds one, it clears everything up to it and restores it. Otherwise it just launches the activity.

On API 16+ later platforms, the support library hands things off to the native call in the Activity API. According to the docs:

public boolean navigateUpTo (Intent upIntent)

Navigate from this activity to the activity specified by upIntent, finishing this activity in the process. If the activity indicated by upIntent already exists in the task’s history, this activity and all others before the indicated activity in the history stack will be finished.

If the indicated activity does not appear in the history stack, this will finish each activity in this task until the root activity of the task is reached, resulting in an “in-app home” behavior. This can be useful in apps with a complex navigation hierarchy when an activity may be reached by a path not passing through a canonical parent activity.

From that, it is unclear whether or not it will launch the activity specified in upIntent if it is not in the back stack. Reading the source code doesn’t help clarify things either. However, judging from the behavior your app is showing, it appears that it does not attempt to launch the upIntent activity.

Regardless of which implementation is right or wrong, the end result is that you want the FLAG_ACTIVITY_CLEAR_TOP behavior, not the native API 16 behavior. Unfortunately this means that you will have to duplicate the support library approximation.

Which is Correct?

Disclaimer: I don’t work for Google, so this is my best guess.

My guess is that the API 16+ behavior is the intended behavior; it’s a builtin implementation that has access to the Android internals and can do things that are not possible through the API. Pre-API 16, I don’t think it was possible to unwind the backstack in this fashion other than by using intent flags. Thus, the FLAG_ACTIVITY_CLEAR_TOP flag is the closest approximation of the API 16 behavior available to pre-API 16 platforms.

Unfortunately the result is that between the native implementation and the support library implementation there is a violation of the principle of least astonishment in your scenario. That leads me to wonder if this is an unanticipated use of the API. I.e., I wonder if Android expects that you to traverse the full navigation path to an activity, not jump directly to it.

Just in Case

Just to avoid one possible misconception, it is possible that someone might expect shouldUpRecreateTask to magically determine that the parent isn’t in the back stack and go through the process of synthesizing everything for you using the TaskStackBuilder.

However, shouldUpRecreateTask basically determines if your activity was launched directly by your app (in which case it returns false), or if it was launched from another app (in which case it returns true). From this book, the support library checks if the intent’s “action” is not ACTION_MAIN (I don’t fully understand that), whereas on API 16 platforms it performs this check based on task affinity. Still, in the case of this app, things work out to shouldUpRecreateTask returning false.

Leave a Comment