43

Is there a way for an Activity to find out who (i.e. class name) has sent an Intent? I'm looking for a generic way for my Activity to respond to a received intent by sending one back to the sender, whoever that may be.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
zer0stimulus
  • 22,306
  • 30
  • 110
  • 141

8 Answers8

34

There may be another way, but the only solution I know of is having Activity A invoke Activity B via startActivityForResult(). Then Activity B can use getCallingActivity() to retrieve Activity A's identity.

Andy Zhang
  • 8,285
  • 1
  • 37
  • 24
  • 1
    We're working on a library for verifying the sender and receiver of `Intent`s that uses this technique to start with, since its the simplest. You can find out more here https://dev.guardianproject.info/projects/trustedintents/wiki – Hans-Christoph Steiner Jul 30 '14 at 18:30
  • 3
    How to use the getCallingActivity()? I've tried to use it to set text like this: `txt.setText(getCallingActivity());` but it shows 'null'. What can I do in order to make it show the name of the calling activity? – Ido Naveh Dec 13 '15 at 14:33
  • If the calling activity is not expecting a result (that is it did not use the startActivityForResult form that includes a request code), then the calling package will be null – mehdigriche Apr 23 '21 at 12:06
27

Is it an external app you receive the intent from? You could use the getReferrer() method of the activity class

A simple example: I opened google map app to share some location with my app by using the share option of google maps. Then my app opens and this method call in the Activity:

 this.getReferrer().getHost()

will return:

 com.google.android.apps.maps

see documentation here: https://developer.android.com/reference/android/app/Activity.html#getReferrer()

Note that this requires API 22. For older Android versions see answer from ajwillliams

donfuxx
  • 11,277
  • 6
  • 44
  • 76
  • 1
    What if your app was already opened and remains in the background while you use the share function in google maps. Will it still return the packagename of google maps? – mathew11 Jun 07 '17 at 14:50
  • 1
    getReferrer() requires API22 (Android 5.1). What to do in lower versions like 4.4? – Damn Vegetables Aug 31 '17 at 15:03
  • Some code to check for API22: String startedFrom = null; if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { startedFrom = this.getReferrer().getHost(); } – FrankKrumnow Nov 14 '17 at 11:20
  • In the docs, it was noted that “this is not a security feature — you cannot trust the referrer information, applications can spoof it.” – Muntashir Akon Aug 21 '23 at 12:04
13

A technique I use is to require the application sending the relevant Intent to add a PendingIntent as a Parcelable extra; the PendingIntent can be of any type (service, broadcast, etc.). The only thing my service does is call PendingIntent.getCreatorUid() and getCreatorPackage(); this information is populated when the PendingIntent is created and cannot be forged by the app so I can get the info about an Intent's sender. Only caveat is that solution only works from Jellybean and later which is my case. Hope this helps,

  • How do you require them to send a PendingIntent? I don't see an intent-filter option for that. Do you just drop intents that don't have one as an extra? – TBridges42 Jul 15 '15 at 12:48
  • 1
    Looks like a bad idea at first sight. Couldn't a malicious app forge the `PendingIntent`? Granted, `PendingIntent` has no public constructor but `readPendingIntentOrNullFromParcel` looks like it could do the trick, no? – Giszmo Oct 17 '16 at 16:55
  • 1
    @Giszmo well the getCreatorUid() docs state that it can't be spoofed. Not sure how serialization is handled, but there must be some special security measure, such as signing the serialized contents with a private key owned by the creator. That said, as the getCreatorUid() docs point out, you still have to be careful with this strategy. Just because some package X has a PendingIntent created by Y doesn't mean that X == Y. Y might have sent the PendingIntent to X for some unrelated purpose. And Y sending a PendingIntent to X means that Y trusts X with that particular action, not generally. – Daniel Lubarov Aug 30 '17 at 18:00
11

This isn't incredibly direct but you can get a list of the recent tasks from ActivityManager. So the caller would essentially be the task before yours and you can fetch info on that task.

Example usage:

ActivityManager am = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.RecentTaskInfo> recentTasks = am.getRecentTasks(10000,ActivityManager.RECENT_WITH_EXCLUDED);

The above will return a list of all the tasks from most recent (yours) to the limit specified. See docs here for the type of info you can get from a RecentTaskInfo object.

ajwillliams
  • 111
  • 1
  • 2
  • 3
    getRecentTasks is deprecated in API 21, and will no longer work for stacks that are not owned by your app. See http://developer.android.com/reference/android/app/ActivityManager.html#getRecentTasks(int, int) – Ian Wong Aug 31 '15 at 22:25
6

Generally you don't need to know this. If the calling activity uses startActivityForResult(Intent, int), the callee can use setResult(int, Intent) to specify an Intent to send back to the caller. The caller will receive this Intent in its onActivityResult(int, int, Intent) method.

adamp
  • 28,862
  • 9
  • 81
  • 69
  • 2
    The intent from `setResult` is only sent once the child Activity is closed. I would like to be able to arbitrarily send intents back to parent activity while child activity is operating. – zer0stimulus Jul 22 '10 at 22:10
  • @zer0stimulus You can create an app service that is running in the background to parse the result. – Chef Pharaoh Mar 12 '19 at 05:13
0

Based on your question, since you want to send an intent back to the sender startActivityForResult is a better choice than what I am going to suggest. But I needed to start activity B when a notification is clicked by the user and execute some code in activity B only if the sender activity is activity A. This is how I did it quite simply.

Inside Activity A:

String senderName = this.getClass().getSimpleName();        
Intent clickIntent = new Intent(ActivityA.this, ActivityB.class);
clickIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
clickIntent.putExtra("SENDER_CLASS_NAME", senderName);

//I use PendingIntent to start Activity B but you can use what you like such as this.startActivity(clickIntent);
PendingIntent.getActivity(ActivityA.this, NOTIFICATION_ID, clickIntent, PendingIntent.FLAG_ONE_SHOT);

Inside Activity B:

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

    if (savedInstanceState == null) {
        Bundle bundle = getIntent().getExtras();
        if (bundle != null) {
            if(bundle.containsKey("SENDER_CLASS_NAME")){
                String senderName = bundle.getString("SENDER_CLASS_NAME");                  
                //Execute some code
                Log.d("GCM", "Notifications clicked");
                }
            }
        }
    }
Ali Kazi
  • 1,561
  • 1
  • 15
  • 22
0

In my case, neither the accepted here and another most voted answer works perfectly.

Activity.getCallerActivity() works only for the sender which starts your activity by startActivityForResult, meaning that if the sender is also in your app and you have full control, it works, but not every external app starts others in that way.

Another most voted answer provides the solution for external app, but it too has issue. First I would prefer getAuthority() instead of getHost(), secondly, if the sender is a browser kind of app, like Chrome, both host and authority will give you the browsing web page's address host, such as www.google.com, instead of the app itself. So it depends on how you define 'sender', if you need to find out which web page starts you, the authority/host is good enough, but if you need to find out which app starts you, I am afraid authority/host can be trusted only when getScheme() gives you android-app instead of http.

ZhouX
  • 1,866
  • 18
  • 22
-1

Use UsageStatsManager and the old RecentTaskInfo to get the intent sender for OnCreate or onNewIntent:

    public static String getTopMostThirdPartyPackage(Context context) {
        String thisPak = null, tmp, top = null;
        try {
            thisPak = context.getPackageName();
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
                UsageStatsManager man = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
                long now = System.currentTimeMillis();
                UsageEvents uEvts = man.queryEvents(now - 5000,now); // query in 5 sec
                UsageEvents.Event e = new UsageEvents.Event();
                while (uEvts.getNextEvent(e)){
                    tmp = e.getPackageName();
                    if (!thisPak.equals(tmp)) {
                        top = tmp;
                        break;
                    }
                }
            } else {
                ActivityManager man = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
                List<ActivityManager.RecentTaskInfo> tasks = man.getRecentTasks(3, 0);
                for(ActivityManager.RecentTaskInfo info:tasks) {
                    tmp = info.baseIntent.getComponent().getPackageName();
                    if (!thisPak.equals(tmp)) {
                        top = tmp;
                        break;
                    }
                }
            }
        } catch (Exception e) {
        }
        return top;
    }

permissions :

    <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
    <uses-permission android:name="android.permission.GET_TASKS" />
        intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
        startActivity(intent);
KnIfER
  • 712
  • 7
  • 13