47

I have two activities

  • MainActivity
  • DeepLinkActivity

I set up everything to use the NavUtils for navigating up like advised here, here and here.

What I want to achieve is:

  1. Start DeepLinkActivity via a deep link
  2. Press up
  3. Go to MainActivity

Everything works nicely as long as there is any task of my app in the recent apps.

However, when I swipe away my app from the recent apps, it behaves like this:

  1. Swipe away my app from recent apps
  2. Start DeepLinkActivity via a deep link
  3. Press up
  4. My app closes, like when pressing back

I debugged the code, and found out, that NavUtils.shouldUpRecreateTask() returns false. upIntent has everything set to normal, like my Component set. But still, NavUtils.navigateUpTo() behaves just like a call to finish(). No log statement, nothing.

Any ideas, how to fix that?

AndroidManifest.xml

<activity
    android:name=".DeepLinkActivity"
    android:parentActivityName="my.package.MainActivity">
    <meta-data
        android:name="android.support.PARENT_ACTIVITY"
        android:value="my.package.MainActivity"/>
    <intent-filter>
        <!-- Some intent filter -->
    </intent-filter>
</activity>

DeepLinkActivity.java

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}

----- Edit -----

I realized that a few Google Apps are broken in the same way. If you jump e.g. to Contacts from search, press up in AB and you'll find yourself on the home screen instead of the contacts app. (API19/cm11)

flx
  • 14,146
  • 11
  • 55
  • 70
  • Does this line execute ever?? return true; – SweetWisher ツ Dec 12 '13 at 04:31
  • Well, the debugger in Android Studio jumps from `NavUtils.navigateUpTo(this, upIntent);` to `return super.onOptionsItemSelected(item);` but I guess/hope, that is a bug. Anyway, the Intent should be launched in `NavUtils.navigateUpTo(this, upIntent);` – flx Dec 12 '13 at 04:46
  • Android studio even tells me that the line of `return true` does not have executable code. o.O – flx Dec 12 '13 at 04:49
  • How are you starting DeepLinkActivity? Can you post some code? – IsaacCisneros Dec 15 '13 at 01:47
  • I has some intent filter and is started by launching a URL from the browser or mail app. – flx Dec 15 '13 at 03:22
  • Fix, check my answer, try declaring an Action inside your intent-filter, you can check directly that action to know if you should "Recreate" those tasks for up navigation. Google's code just do the same in support library – noni Dec 18 '13 at 19:46
  • I believe this is a bug within the native ActivityManager. More details below: http://stackoverflow.com/a/23400135/204480 – James Wald May 01 '14 at 00:06
  • The bug may have been fixed. As of 9/3/14, on Android 4.4.4, shouldUpRecreateTask returns true even if the main app was swiped away. – Erhannis Sep 04 '14 at 01:45
  • Clarified once - saving days continuously Thx guys – Alex Oct 19 '16 at 11:20

9 Answers9

59

My solution to OPs problem:

public void navigateUp() {
    final Intent upIntent = NavUtils.getParentActivityIntent(this);
    if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) {
        Log.v(logTag, "Recreate back stack");
        TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent).startActivities();
    } else {
        NavUtils.navigateUpTo(this, upIntent);
    }
}

isTaskRoot() will return true if DeepLinkActivity is the root of a task (initial launch of application or application was previously terminated through task manager). This way I'm not loosing existing back stack if activity was launched through link when applications task was already in the foreground.

Sokolof
  • 2,271
  • 2
  • 19
  • 30
22

I think that method is bugged. I've read support library source code, and that method check for intent's action. It only works when your App was previously created..as you've described, if you kill it from Apps preview, shouldUp method stops working.

I've fixed this using my own "shouldUpRecreateTask". When I receive a Notification that creates directly an Activity (Like your behaviour), I send from my BroadCastReceiver a custom Action inside the intent. Then, in my Method I do the next thing:

private final boolean shouldUpRecreateTask(Activity from){
    String action = from.getIntent().getAction();
    return action != null && action.equals(com.xxxxxx.activities.Intent.FROM_NOTIFICATION);
}

..........................

 if (shouldUpRecreateTask(this)) {
      TaskStackBuilder.create(this)
       .addNextIntentWithParentStack(upIntent)
       .startActivities();
 } else {
       fillUpIntentWithExtras(upIntent);
       NavUtils.navigateUpTo(this, upIntent);
 }
noni
  • 2,927
  • 19
  • 18
  • That is basically solution as I found with my own answer, but with better explanation. Thanks for the insight. – flx Dec 19 '13 at 03:22
  • 20
    Instead of adding custom action you might use Activity#isTaskRoot method. My up nav looks like this and I haven't found problem with it: http://pastebin.com/dTz1UEH5 Intent upIntent = NavUtils.getParentActivityIntent(this); if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) { TaskStackBuilder.create(this) .addNextIntentWithParentStack(upIntent) .startActivities(); } else { NavUtils.navigateUpTo(this, upIntent); } – Mikooos Jul 20 '14 at 20:28
  • 1
    Sending a custom action or checking if isTaskRoot() will recreate the parent activity, but in my case when a new notification arrives, tapping in the notification it won't launch the activity anymore. It just does nothing at all. – MrBrightside Aug 06 '15 at 16:33
7

It looks like NavUtils.shouldUpRecreateTask(this, upIntent) is not prepared for this special case.

My current workaround is checking, if the Activity was deep linked and force a new task stack for cases like this.

In my case, I pass around objects in EXTRAs internally. But an ACTION and Uri is set if the Activity is started from outside by following a link to a specific URL or by touching NFC aware devices.

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)
                    || getIntent().getAction() != null) { // deep linked: force new stack
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}
flx
  • 14,146
  • 11
  • 55
  • 70
  • Thanks. I am going with this idea by setting a boolean extra to the intent 'is_deeplink' and checking for it to create new task. – Thupten May 23 '15 at 14:47
4

Do you use an <activity-alias> as your MAIN / LAUNCHER? When I change the <activity-alias> to an <activity>, up navigation works correctly. For some odd reason, navigation does not work properly when aliases are involved.

There is also a weird UI glitch which indicates that the native ActivityManager has a bug. After navigating up to the app's main activity using navigateUpTo(), press the device's back key. The current app will perform the activity exit animation and then immediately afterwards, the next-most-recent app will also perform an activity exit animation. This happens even if you launched into the current app from Android's Home screen. If you clear all recent apps and try the nav-up-then-back steps again, a random (i.e. unexpected) app will be presented during the exit animation.

Other apps should never be presented in these cases. It seems as though the activity manager is not managing the history stack correctly.

The offending bug may be found within the following method which appears at the bottom of this diff:

+    public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode,
+            Intent resultData) {
+        ComponentName dest = destIntent.getComponent();
+
+        synchronized (this) {
+            ActivityRecord srec = ActivityRecord.forToken(token);
+            ArrayList<ActivityRecord> history = srec.stack.mHistory;
+            final int start = history.indexOf(srec);
+            if (start < 0) {
+                // Current activity is not in history stack; do nothing.
+                return false;
+            }
+            int finishTo = start - 1;
+            ActivityRecord parent = null;
+            boolean foundParentInTask = false;
+            if (dest != null) {
+                TaskRecord tr = srec.task;
+                for (int i = start - 1; i >= 0; i--) {
+                    ActivityRecord r = history.get(i);
+                    if (tr != r.task) {
+                        // Couldn't find parent in the same task; stop at the one above this.
+                        // (Root of current task; in-app "home" behavior)
+                        // Always at least finish the current activity.
+                        finishTo = Math.min(start - 1, i + 1);
+                        parent = history.get(finishTo);
+                        break;
+                    } else if (r.info.packageName.equals(dest.getPackageName()) &&
+                            r.info.name.equals(dest.getClassName())) {
+                        finishTo = i;
+                        parent = r;
+                        foundParentInTask = true;
+                        break;
+                    }
+                }
+            }
+
+            if (mController != null) {
+                ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0);
+                if (next != null) {
+                    // ask watcher if this is allowed
+                    boolean resumeOK = true;
+                    try {
+                        resumeOK = mController.activityResuming(next.packageName);
+                    } catch (RemoteException e) {
+                        mController = null;
+                    }
+
+                    if (!resumeOK) {
+                        return false;
+                    }
+                }
+            }
+            final long origId = Binder.clearCallingIdentity();
+            for (int i = start; i > finishTo; i--) {
+                ActivityRecord r = history.get(i);
+                mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData,
+                        "navigate-up");
+                // Only return the supplied result for the first activity finished
+                resultCode = Activity.RESULT_CANCELED;
+                resultData = null;
+            }
+
+            if (parent != null && foundParentInTask) {
+                final int parentLaunchMode = parent.info.launchMode;
+                final int destIntentFlags = destIntent.getFlags();
+                if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE ||
+                        parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK ||
+                        parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP ||
+                        (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) {
+                    parent.deliverNewIntentLocked(srec.app.uid, destIntent);
+                } else {
+                    try {
+                        ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo(
+                                destIntent.getComponent(), 0, UserId.getCallingUserId());
+                        int res = mMainStack.startActivityLocked(srec.app.thread, destIntent,
+                                null, aInfo, parent.appToken, null,
+                                0, -1, parent.launchedFromUid, 0, null, true, null);
+                        foundParentInTask = res == ActivityManager.START_SUCCESS;
+                    } catch (RemoteException e) {
+                        foundParentInTask = false;
+                    }
+                    mMainStack.requestFinishActivityLocked(parent.appToken, resultCode,
+                            resultData, "navigate-up");
+                }
+            }
+            Binder.restoreCallingIdentity(origId);
+            return foundParentInTask;
+        }
+    }
James Wald
  • 13,626
  • 5
  • 52
  • 63
3

I found the following worked for me. If the Activity was deep linked from another activity or notification the stack would be created as you navigate up otherwise, the activities are just brought to the front.

case android.R.id.home:
Intent upIntent = NavUtils.getParentActivityIntent(this);
upIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
startActivity(upIntent);
finish();
return true;

Provided you have

android:parentActivityName="parent.activity"

in your manifest

David
  • 5,203
  • 2
  • 16
  • 17
2

It doesn't work for me too. So I handle up this way:

   @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {

                Intent intent = new Intent(this, MainActivity.class);
                startActivity(intent);
                finish();
                return true;

        }
        return super.onOptionsItemSelected(item);
    }

My MainActivity is marked as singleTask in android manifest

android:launchMode="singleTask"
Martin Vandzura
  • 3,047
  • 1
  • 31
  • 63
2

If you go to your activity from a notification you'll have to create the stack when creating the notification, checkout this answer: NavUtils.shouldUpRecreateTask fails on JellyBean

Community
  • 1
  • 1
Ciprian
  • 2,879
  • 3
  • 28
  • 28
1

Try this :

@Override
public boolean onOptionsItemSelected(final MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
                // create new task
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                // Stay in same task
                NavUtils.navigateUpTo(this, upIntent);
            }
            break;
        default:
            return super.onOptionsItemSelected(item);
    }
    return true;
}
flx
  • 14,146
  • 11
  • 55
  • 70
SweetWisher ツ
  • 7,296
  • 2
  • 30
  • 74
0

Try the following answer working for me in both case start activity from notification and start an activity from parent activity.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:
            Intent upIntent = NavUtils.getParentActivityIntent(this);
            if (NavUtils.shouldUpRecreateTask(this, upIntent) || isTaskRoot()) {
                TaskStackBuilder.create(this).addNextIntentWithParentStack(upIntent)
                        .startActivities();
            } else {
                finish();
            }
            return true;
        default:
            return super.onOptionsItemSelected(item);
    }
}
user2837615
  • 296
  • 1
  • 5
  • 12