14

i am using this solution : How to make notification intent resume rather than making a new intent?

I works fine when i run my app normally.. however my app has a share function.

When i select the images i want to share from gallery app and it open it in my activity, i create a notification in the activity. I want that when user clicks on the notification it opens the existing activity (opened by the gallery app)

The problem is that when i click on the notification it does not resume that activity but instead open a new instance of the activity or another instance already opened previously

Edit: More explanation

My app is called ShareApp and the activity is called ShareActivity the problem is that when i open the ShareActivity via gallery app, an instance of the ShareActivity is created at the top of the gallery app task's stack. Now when i create a notification that points to my ShareActivity, i use the intent:

Intent intent = new Intent(this, ShareActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

problem is that when i click on the notification, it points to the instance of the ShareActivity in the the ShareApp task's stack instead of the gallery app task's stack..

any idea how to point to the correct task's stack??

edit 2: my code

        int defaults = Notification.FLAG_NO_CLEAR;
        NotificationCompat.Builder mBuilder =
                    new NotificationCompat.Builder(this)
                    .setSmallIcon(R.drawable.ic_launcher)
                    .setContentTitle(getString(R.string.app_name))
                        .setContentText(text)
                        .setDefaults(defaults);
        Intent intent = new Intent(this, this.getClass());
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);

        mBuilder.setContentIntent(pendingIntent);
        NotificationManager mNotificationManager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        mNotificationManager.notify(_ID, mBuilder.build());

Edit 3 : adb shell dumpsys activity (after applying the code by David https://stackoverflow.com/a/16901603/1334268)

My app is called shareApp and my activity is the ShareActivity

Before clicking on notification:

Main stack:
    TaskRecord{41965708 #6 A com.android.gallery3d}
    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.gallery3d/.app.Gallery bnds=[364,50][108,108]}
      Hist #2: ActivityRecord{417ccc28 com.yeahman.shareapp/.ShareActivity}
        Intent { act=android.intent.action.SEND_MULTIPLE typ=image/* cmp=com.yeahman.shareapp/.ShareActivity (has extras) }
        ProcessRecord{41c868d0 6088:com.yeahman.shareapp/10116}
      Hist #1: ActivityRecord{4135e540 com.android.gallery3d/.app.Gallery}
        Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.gallery3d/.app.Gallery bnds=[364,50][108,108] }

After clicking notification:

 Main stack:
    TaskRecord{41965708 #6 A com.android.gallery3d}
    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.gallery3d/.app.Gallery bnds=[364,50][108,108]}
      Hist #3: ActivityRecord{4169f358 com.android.gallery3d/.app.Gallery}
        Intent { flg=0x20000000 cmp=com.android.gallery3d/.app.Gallery bnds=[0,205][480,301] }
        ProcessRecord{4175dd28 5808:com.android.gallery3d/10036}
      Hist #2: ActivityRecord{417ccc28 com.yeahman.shareapp/.ShareActivity}
        Intent { act=android.intent.action.SEND_MULTIPLE typ=image/* cmp=com.yeahman.shareapp/.ShareActivity (has extras) }
        ProcessRecord{41c868d0 6088:com.yeahman.shareapp/10116}
      Hist #1: ActivityRecord{4135e540 com.android.gallery3d/.app.Gallery}
        Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.android.gallery3d/.app.Gallery bnds=[364,50][108,108] }
Community
  • 1
  • 1
yeahman
  • 2,737
  • 4
  • 21
  • 25

5 Answers5

34
    Notification.Builder mBuilder =
            new Notification.Builder(this)
            .setSmallIcon(R.drawable.cmplayer)
            .setContentTitle("CoderoMusicPlayer")
            .setContentText("PLayer0!");

    Intent resultIntent = new Intent(this, AndroidBuildingMusicPlayerActivity.class);
    resultIntent.setAction(Intent.ACTION_MAIN);
    resultIntent.addCategory(Intent.CATEGORY_LAUNCHER);

    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
            resultIntent, 0);

    mBuilder.setContentIntent(pendingIntent);
    NotificationManager mNotificationManager =
        (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
    mNotificationManager.notify(1, mBuilder.build());

Just Copy the code and paste it in your main launcher activity.

Hasan Masud
  • 982
  • 9
  • 17
  • perfect working solution specially for utility apps where you have a single activity – Ravi Oct 28 '14 at 07:25
  • @HasanMasud, great and thanks , it worked.. could you please explain, what the code does? – Selva Feb 25 '16 at 17:48
  • 2
    Its very simple. You can show the icon and description of your app in the task bar. if anyone click the icon from the task bar he will go back to the current activity running in the background. – Hasan Masud Feb 29 '16 at 04:31
  • 3
    Beautiful Didn't think it would but worked!! It's those two lines `action and category` – sud007 Jun 05 '16 at 10:16
7

If you have multiple instances of ShareActivity active in different tasks, there is no way for you to tell Android which task to reactivate by launching your activity. It sounds to me like what you want to do, in this case, is to launch not your activity, but the Gallery application. If the Gallery app is already running and your activity is on the top of the task stack, then all you need to do is to bring the Gallery app to the foreground. To do this, create a "launch Intent" for the Gallery app and make sure to set Intent.FLAG_ACTIVITY_NEW_TASK on the Intent. You should be able to get a "launch Intent" from the PackageManager by using getLaunchIntentForPackage()

EDIT: Example code using ActivityManager

You might try this:

// Get the root activity of the task that your activity is running in
ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
ActivityManager.RunningTaskInfo task = tasks.get(0); // Should be my task
ComponentName rootActivity = task.baseActivity;

// Now build an Intent that will bring this task to the front
Intent intent = new Intent();
intent.setComponent(rootActivity);
// Set the action and category so it appears that the app is being launched
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);

NOTE: You will need GET_TASKS permission for this. I haven't tried this and can't guarantee it will work. The use of getRunningTasks() is discouraged by the documentation, but that doesn't mean that it won't suit your purposes.

EDIT added action/category to the Intent

NOTE: I actually built a small app to test this. I needed to add the ACTION=MAIN and CATEGORY=LAUNCHER to the Intent (which would be there if you used PackageManager.getLaunchIntentForPackage(). I've got an HTC One S, so on my device the "Gallery" app is actually called com.htc.album/.AlbumMain.ActivityMainDropList, but this should still work for you.

David Wasser
  • 93,459
  • 16
  • 209
  • 274
  • do you have an example using the PackageManager? what if i am using another gallery app or file manager app instead of the default gallery app? – yeahman Jun 03 '13 at 18:54
  • Do you need to have `ShareActivity` exist in the Gallery task? If not, you could declare `ShareActvity` as `launchMode="singleTask"`. This would create `ShareActivity` in a new task and you wouldn't have the problem of having your activity present in multiple tasks. You might try that and see if it solves your problem. – David Wasser Jun 04 '13 at 18:35
  • yup i know it's possible to do it via singletask thx but i would prefer having a new instance each time. with singletask, the annoyance i get is when i open the shareactivity via gallery app and i enter my app, it opens the shareactivity opened via gallery.. it is not very intuitive – yeahman Jun 04 '13 at 19:02
  • 1
    Since you indicated that you don't know what app might have started your activity, I've added an example using the `ActivityManager` to determine the root activity of the current task. I've not tried this, but it may work for you. Let me know how it goes. – David Wasser Jun 04 '13 at 19:28
  • i tried your code thx but when i click on the notification, it opens a the gallery (i don't know if it has created a new instance or resume previous instance) instead of showing the shareactivity opened in the gallery app. :( – yeahman Jun 04 '13 at 19:49
  • 1
    You can use `adb shell dumpsys activity` to have a look at the tasks in the system and the activities that are in them. This will give you a good idea of what is going on. Have a look before you click on the notification and then again after you click on the notification. Maybe that will give some insight into what is going on. – David Wasser Jun 04 '13 at 20:26
  • ok i tried adb shell dumpsys activity. When i click on the notification, it creates a new instance of gallery on top of the gallery app main stack.... the "Intent.FLAG_ACTIVITY_NEW_TASK" flag isn't suppose to bring the existing main stack on foreground instead of creating a new instance?? – yeahman Jun 08 '13 at 08:03
  • is it related to the bug described there : http://stackoverflow.com/questions/9772927/flag-activity-new-task-clarification-needed? – yeahman Jun 08 '13 at 08:13
  • I don't think it is related to that bug, but it may be related to another bug. How did you start the gallery application? Can you post the (before and after using notification) output of `adb shell dumpsys activity` somewhere where we can look at it? – David Wasser Jun 08 '13 at 10:45
  • i am thinking about redesigning my activity using services and binding instead as the solution seems that it will be quite "hackish" but i am still curious to know how to do it.. i learned a lot of things and i have a much better understanding of tasks and stacks in android in trying to find a solution to this problem and with your help. thx – yeahman Jun 08 '13 at 16:34
  • Try rebooting your phone and trying this again. The activity stack already has multiple instances of both the Gallery activity and your ShareActivity, even before you click on the notification! Let me know if there's any different behaviour – David Wasser Jun 08 '13 at 20:06
  • ok you were right; i rebooted and i have updated my dumpsys in my main post. – yeahman Jun 09 '13 at 07:16
  • You haven't tried the code I provided, you are using `getLaunchIntentForPackage()`. Could you try the other code I provided? This is still a mystery to me... – David Wasser Jun 10 '13 at 08:15
  • i am using your example "Example code using ActivityManager" not getLaunchIntentForPackage() – yeahman Jun 10 '13 at 16:14
  • 1
    I created a small test program. I ran it on an HTC One SV. I was able to reproduce your problem. I needed to add ACTION=MAIN and CATEGORY=LAUNCHER to the `Intent` for the notification. Now it does what you want (ie: clicking on the notification returns to the Gallery task with your `ShareActivity` on top). – David Wasser Jun 12 '13 at 11:41
  • it works!!! thx very much man! But i still doesn't understand why with Intent.FLAG_ACTIVITY_NEW_TASK it does not work? according to documentation isn't it suppose to bring on front current state of the stack of the task in which the activity is found? – yeahman Jun 12 '13 at 16:25
  • 1
    Yes, according to the documentation and based on my own experiences, using `FLAG_ACTIVITY_NEW_TASK` should do exactly that. For some reason it does not. I don't know why. I need to dig some more. But at least you now have a working solution, which is a "good thing". Glad we could help – David Wasser Jun 12 '13 at 16:39
5

Set

android:launchMode="singleTop"

in your manifest.xml for your main activity and see if that helps.

Ken Wolf
  • 23,133
  • 6
  • 63
  • 84
  • You are definitely setting the flags for the pendingIntent? `i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);` – Ken Wolf Jun 02 '13 at 18:38
  • yup! the problem i think is that when the activity is opened from gallery, the activity goes the gallery stack and not my app (not sure if it's correct). So when i click on notification it tries to open the activity from my app's stack rather than from the gallery one... not sure if what i am saying is alright.. i am a bit confused with the stack/instance of android right now – yeahman Jun 02 '13 at 18:41
  • 1
    Yep! it worked. Of course besides implementing `onNewIntent` inside your activity. – Hamzeh Soboh Nov 28 '17 at 14:15
3

This is a solution I've been using for years.

Your problem is that your PendingIntent starts it's target from an appless content.

Your first step is to bring the result of your Intent inside your apps context. To do this have your notification call a broadcast to "ForwardingReceiver"

Intent newIntent = new Intent(context, ForwardingReceiver.class);
...
mBuilder.setContentIntent(PendingIntent.getBroadcast(context, category, newIntent, PendingIntent.FLAG_UPDATE_CURRENT));

The ForwardingReceiver extends BroadcastReceiver and in it's onReceive() method you now have you're apps current context and can use sendOrderedBroadcast() like so:

Intent forwardIntent = new Intent();
forwardIntent.putExtras(intent.getExtras());
forwardIntent.setAction(GenericReceiver.class.getCanonicalName());
context.sendOrderedBroadcast(forwardIntent, null);

Now, the fun part. You need to extend all of your apps Activities from a common Activity parent. You may already do this for logging or analytic. In this CommonActivityParent onResume() you need to register to receive broadcasts at a priority of above 0 (such as 10)

mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        performActionUsingIntent(intent);
    }
};

IntentFilter filter = new IntentFilter();
filter.addAction(GenericReceiver.class.getCanonicalName());
filter.setPriority(10);
registerReceiver(mReceiver, filter);

(Remember to unregister this in onPause())

This will mean that if your app is in the foreground, it will receive the notification click (indirectly) and allow you to perform an action.

However, if you Activity is in the background, it won't have this receiver registered. No problem, your "GenericReceiver" should look something like this

@Override
public void onReceive(Context context, Intent intent) {
    PackageManager pm = context.getPackageManager();
    Intent newIntent = pm.getLaunchIntentForPackage(context.getPackageName());
    newIntent.putExtras(intent.getExtras());
    context.startActivity(newIntent);
}

Which will start your launch activity. This launch activity should have code in it's onCreate() to detect if it's the tasks root. If it isn't it should close immediately (and therefore is never visible to the user). This effectively forces the last open task into the foreground:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (!isTaskRoot()
            && getIntent().hasCategory(Intent.CATEGORY_LAUNCHER)
            && getIntent().getAction() != null
            && getIntent().getAction().equals(Intent.ACTION_MAIN)) {

        // Use this space to add the intent to a static singleton. This will be used in CommonActivityParent to perform an action.
        CommonActivityParent.sIntent = getIntent();
        finish();
        return;
    }
}

To finish, ensure just after your registerReceiver() call above in CommonActivityParent, you perform the following check:

if(sIntent != null) {
     Intent tempIntent = sIntent;
     sIntent = null;
     performActionUsingIntent(tempIntent);
}

An hey presto - You're app will now act as if a notification click is actually a click from within your app. And all it took was a hell of a lot of work!

Hopefully someone has a less convoluted way of doing this.

Graeme
  • 25,714
  • 24
  • 124
  • 186
0

for additional info, dont forget to add pending intent flag to make onNewIntent works!

true:

PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT)

false:

PendingIntent.getActivity(context, 0, resultIntent, 0)
Ryan M
  • 18,333
  • 31
  • 67
  • 74
Jetwiz
  • 642
  • 7
  • 14