7

I'm trying to implement navigation according to: http://developer.android.com/training/implementing-navigation/ancestral.html

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
    // Respond to the action bar's Up/Home button
    case android.R.id.home:
        Intent upIntent = NavUtils.getParentActivityIntent(this);
        if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
            // This activity is NOT part of this app's task, so create a new task
            // when navigating up, with a synthesized back stack.
            TaskStackBuilder.create(this)
                    // Add all of this activity's parents to the back stack
                    .addNextIntentWithParentStack(upIntent)
                    // Navigate up to the closest parent
                    .startActivities();
        } else {
            // This activity is part of this app's task, so simply
            // navigate up to the logical parent activity.
            NavUtils.navigateUpTo(this, upIntent);
        }
        return true;
    }
    return super.onOptionsItemSelected(item);
}

but if (NavUtils.shouldUpRecreateTask(this, upIntent)) { is returning FALSE always.

How is this code useful? In which cases it does return true?

Dandy
  • 303
  • 5
  • 14
  • For example, if I press Home button, play around and go back to my app with task manager, it stills returns false, even there is no more task stack (it takes me back to Task manager, not my main activity). – Dandy Jul 01 '13 at 01:33

2 Answers2

4

On pre-Jelly Bean devices, per the source code of NavUtils, shouldUpRecreateTask is:

public boolean shouldUpRecreateTask(Activity activity, Intent targetIntent) {
    String action = activity.getIntent().getAction();
    return action != null && !action.equals(Intent.ACTION_MAIN);
}

Which uses the Activity's action (i.e., ACTION_VIEW, etc.) to determine if this Activity was launched from an external source. On Jelly Bean+ devices, it uses Activity.shouldUpRecreateTask (source code):

public boolean shouldUpRecreateTask(Intent targetIntent) {
    try {
        PackageManager pm = getPackageManager();
        ComponentName cn = targetIntent.getComponent();
        if (cn == null) {
            cn = targetIntent.resolveActivity(pm);
        }
        ActivityInfo info = pm.getActivityInfo(cn, 0);
        if (info.taskAffinity == null) {
            return false;
        }
        return !ActivityManagerNative.getDefault()
                .targetTaskAffinityMatchesActivity(mToken, info.taskAffinity);
    } catch (RemoteException e) {
        return false;
    } catch (NameNotFoundException e) {
        return false;
    }
}

Which uses Task Affinity to determine if the given Activity's affinity is the same as the affinity it was launched with (i.e., if Gmail launches your Activity, it would be given Gmail's affinity, rather than its own).

NavUtils.navigateUpTo, in all cases, launches an Activity. If it isn't launching the appropriate Activity, then you may need to look at what launch mode you are using and provide details on what platform version it isn't working.

ianhanniballake
  • 191,609
  • 30
  • 470
  • 443
  • I've tested on 2.3.x and 4.1.x and shouldUpRecreateTask is always returning FALSE. Even when I go home, play around and go back to app. I've added `android:taskAffinity="com.my.app.MainActivity"`to both activities in Manifest (is this correct?) and I still get FALSE. Thanks. – Dandy Jul 01 '13 at 14:04
  • @Dandy - and false is the right answer when your app hasn't been launched from another non-launcher app (launchers use the `ACTION_MAIN`). The question is why `navigateUpTo` isn't launching your `Activity`. As I stated, if that is indeed the problem then "you may need to look at what launch mode you are using and provide details on what platform version it isn't working." – ianhanniballake Jul 01 '13 at 16:00
  • Thanks. I solved it by using the ActivityManager and checking for the backStack entries. If my MainActivity is the previous item, I to moveTaskToBack(true), if not, I call a new intent. – Dandy Jul 01 '13 at 16:19
  • 1
    @ianhanniballake you should check [this explanation](http://stackoverflow.com/a/14792752/51966) of why Activity isn't launched – mente Nov 19 '13 at 15:45
  • @Dandy You are correct, I am also getting always false, I launched my activity from gmail with help of the url. this method is buggy? – sam_k Mar 23 '17 at 21:32
  • @ianhanniballake Getting always false from "shouldUpRecreateTask" method even on Nogut device. – sam_k Mar 23 '17 at 21:33
  • @sam_k - are you sure your app is being launched in Gmail's task? If they use [FLAG_ACTIVITY_NEW_TASK](https://developer.android.com/reference/android/content/Intent.html#FLAG_ACTIVITY_NEW_TASK), then you'll be in your task's affinity as per the [affinities documentation](https://developer.android.com/guide/components/activities/tasks-and-back-stack.html#Affinities). – ianhanniballake Mar 23 '17 at 21:40
  • @ianhanniballake what if I set `android:launchMode="singleTask"` in my android manifest for the DeepLinkActivity. Is it still consider as FLAG_ACTIVITY_NEW_TASK? If yes then you are correct. So what could be my potential solution? Should I remove singleTask from the manifest? – sam_k Mar 23 '17 at 23:35
  • @sam_k - `singleTask` is almost always the wrong thing to do so I'd definitely remove it if you have added it. – ianhanniballake Mar 23 '17 at 23:52
  • @ianhanniballake android task and flags documentation are always confusing. I am not able to understand every time. Sometimes few combinations are working and few not. In this case, let me check what I can do – sam_k Mar 23 '17 at 23:57
  • @ianhanniballake Here is the thing: I have activity declared in the manifest named as "DeepLinkActivity", I am not launching this activity from anywhere in my application only external application are launching this, Like Gmail can launch this when there is link(url) which belongs to my application. How should I know Gmail is launching with FLAG_ACTIVITY_NEW_TASK or not? shouldUpRecreateTask is giving always false when there is no application instance running and I am trying to launch DeepLink activity from gmail using deep link url. What's going wrong here? Any idea? – sam_k Mar 28 '17 at 18:14
  • @sam_k - you can compare the Intent's [flags](https://developer.android.com/reference/android/content/Intent.html#getFlags()) to `FLAG_ACTIVITY_NEW_TASK`. – ianhanniballake Mar 28 '17 at 18:20
  • @ianhanniballake Let me debug that immediately – sam_k Mar 28 '17 at 18:20
  • @ianhanniballake It contains FLAG_ACTIVITY_NEW_TASK, I have checked with this expression : `getIntent().getFlags()|Intent.FLAG_ACTIVITY_NEW_TASK`. That means task's affinity is different then my application name? – sam_k Mar 28 '17 at 18:33
0

I solved it by using the ActivityManager and checking for the backStack entries.

I hope it will be usefull for you all.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case android.R.id.home:

            ActivityManager am = (ActivityManager) getApplicationContext().getSystemService(getApplicationContext().ACTIVITY_SERVICE);
            List<RunningTaskInfo> runningTaskInfoList =  am.getRunningTasks(10);
            List<String> backStack = new ArrayList<String>();
            Iterator<RunningTaskInfo> itr = runningTaskInfoList.iterator();
            while(itr.hasNext()){
                RunningTaskInfo runningTaskInfo = (RunningTaskInfo)itr.next();
                String topActivity = runningTaskInfo.topActivity.getShortClassName();
                backStack.add(topActivity.trim());
            }
            if(backStack!=null){
                if(backStack.get(1).equals(".MainActivity")){
                    moveTaskToBack(true); // or finish() if you want to finish it. I don't.
                } else {
                    Intent intent = new Intent(this, MainActivity.class);
                    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                    startActivity(intent);
                    finish();
                }
            }

        break;  
    }
}

And I needed to use:

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

in my Manifest.

It works perfectly, though it's not the solution I would have wanted. Unfortunately, android is still lacking of advanced built-in methods to achieve simple things, and we all have to find the way of everything.

Dandy
  • 303
  • 5
  • 14