How to filter specific apps for ACTION_SEND intent (and set a different text for each app)

AndroidAndroid IntentAndroid Sharing

Android Problem Overview


How can you filter out specific apps when using the ACTION_SEND intent? This question has been asked in various ways, but I haven't been able to gather a solution based on the answers given. Hopefully someone can help. I would like to provide the ability to share within an app. Following Android Dev Alexander Lucas' advice, I'd prefer to do it using intents and not using the Facebook/Twitter APIs.

Sharing using ACTION_SEND intent

Sharing using the ACTION_SEND intent is great, but the problem is (1) I don't want every sharing option there, I'd rather limit it to FB, Twitter, and Email, and (2) I don't want to share the same thing to each sharing app. For example, in my twitter share I'm going to include some mentions and hashtags limited it to 140 chars or less, while the facebook share is going to include a link and a feature image.

Is it possible to limit the options for ACTION_SEND (share) intent? I've seen something about using PackageManager and queryIntentActivities, but haven't been able to figure out the connection between the PackageManager and the ACTION_SEND intent.

OR

Rather than filter the sharing apps, my problem could also be solved if I could use the ACTION_SEND intent to go directly to facebook or twitter rather than popping up the dialog. If that were the case then I could create my own dialog and when they click "Facebook" create a Facebook-specific intent and just send them all the way to Facebook. Same with Twitter.

OR is it not possible? Are the Facebook and Twitter APIs the only way?

Android Solutions


Solution 1 - Android

To my knowledge, StackOverflow has lots of people asking this question in various ways, but nobody has answered it completely yet.

My spec called for the user to be able to choose email, twitter, facebook, or SMS, with custom text for each one. Here is how I accomplished that:

public void onShareClick(View v) {
	Resources resources = getResources();

	Intent emailIntent = new Intent();
	emailIntent.setAction(Intent.ACTION_SEND);
	// Native email client doesn't currently support HTML, but it doesn't hurt to try in case they fix it
	emailIntent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_native)));
	emailIntent.putExtra(Intent.EXTRA_SUBJECT, resources.getString(R.string.share_email_subject));
	emailIntent.setType("message/rfc822");
	
	PackageManager pm = getPackageManager();
	Intent sendIntent = new Intent(Intent.ACTION_SEND);		
	sendIntent.setType("text/plain");


	Intent openInChooser = Intent.createChooser(emailIntent, resources.getString(R.string.share_chooser_text));

	List<ResolveInfo> resInfo = pm.queryIntentActivities(sendIntent, 0);
    List<LabeledIntent> intentList = new ArrayList<LabeledIntent>();		
	for (int i = 0; i < resInfo.size(); i++) {
	    // Extract the label, append it, and repackage it in a LabeledIntent
	    ResolveInfo ri = resInfo.get(i);
	    String packageName = ri.activityInfo.packageName;
	    if(packageName.contains("android.email")) {
	    	emailIntent.setPackage(packageName);
	    } else if(packageName.contains("twitter") || packageName.contains("facebook") || packageName.contains("mms") || packageName.contains("android.gm")) {
		    Intent intent = new Intent();
		    intent.setComponent(new ComponentName(packageName, ri.activityInfo.name));
		    intent.setAction(Intent.ACTION_SEND);
		    intent.setType("text/plain");
		    if(packageName.contains("twitter")) {
		    	intent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.share_twitter));
		    } else if(packageName.contains("facebook")) {
		    	// Warning: Facebook IGNORES our text. They say "These fields are intended for users to express themselves. Pre-filling these fields erodes the authenticity of the user voice."
		    	// One workaround is to use the Facebook SDK to post, but that doesn't allow the user to choose how they want to share. We can also make a custom landing page, and the link
		    	// will show the <meta content ="..."> text from that page with our link in Facebook.
		    	intent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.share_facebook));
		    } else if(packageName.contains("mms")) {
		    	intent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.share_sms));
		    } else if(packageName.contains("android.gm")) { // If Gmail shows up twice, try removing this else-if clause and the reference to "android.gm" above
		    	intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(resources.getString(R.string.share_email_gmail)));
		    	intent.putExtra(Intent.EXTRA_SUBJECT, resources.getString(R.string.share_email_subject));			    
		    	intent.setType("message/rfc822");
		    }
		    
		    intentList.add(new LabeledIntent(intent, packageName, ri.loadLabel(pm), ri.icon));
	    }
	}

    // convert intentList to array
	LabeledIntent[] extraIntents = intentList.toArray( new LabeledIntent[ intentList.size() ]);

	openInChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents);
	startActivity(openInChooser);		
}

I found bits of how to do this in various places, but I haven't seen all of it in one place anywhere else.

Note that this method also hides all the silly options that I don't want, like sharing over wifi and bluetooth.

Hope this helps someone.

Edit: In a comment, I was asked to explain what this code is doing. Basically, it's creating an ACTION_SEND intent for the native email client ONLY, then tacking other intents onto the chooser. Making the original intent email-specific gets rid of all the extra junk like wifi and bluetooth, then I grab the other intents I want from a generic ACTION_SEND of type plain-text, and tack them on before showing the chooser.

When I grab the additional intents, I set custom text for each one.

Edit2: It's been awhile since I posted this, and things have changed a bit. If you are seeing gmail twice in the list of options, try removing the special handling for "android.gm" as suggested in a comment by @h_k below.

Since this one answer is the source of nearly all my stackoverflow reputation points, I have to at least try to keep it up to date.

Solution 2 - Android

If you want a customized option then you should not rely on the default dialog provided by android for this action.

What you need to do instead is roll out your own. You will need to query the PackageManager on which packages handle the action you require and then based on the reply, you apply filtering and customized text.

Specifically, take a look at the method queryIntentActivities of the PackageManager class. You build the intent that would launch the default dialog (the ACTION_SEND intent), pass that to this method and you will receive a list of objects that contain info on the activities that can handle that intent. Using that, you can choose the ones you want.

Once you build your list of packages you want to present, you need to build your own list dialog (preferably an activity with the dialog theme) which will display that list.

One thing to note though is that it's very hard to make that custom dialog look like the default one. The problem is that the theme used in that dialog is an internal theme and cannot be used by your application. You can either try to make it as similar to the native one as you want or go for a completely custom look (many apps do that like the gallery app etc)

Solution 3 - Android

Found a solution that works for me looking here (see the third comment on the first answer). This code looks for a valid twitter client and uses it to post the tweet. Note: It does not give you an Intent with the various Twitter clients and allow you to choose.

Share using twitter:

Intent shareIntent = findTwitterClient(); 
shareIntent.putExtra(Intent.EXTRA_TEXT, "test");
startActivity(Intent.createChooser(shareIntent, "Share"));

Calling this method:

public Intent findTwitterClient() {
	final String[] twitterApps = {
			// package // name - nb installs (thousands)
			"com.twitter.android", // official - 10 000
			"com.twidroid", // twidroid - 5 000
			"com.handmark.tweetcaster", // Tweecaster - 5 000
			"com.thedeck.android" }; // TweetDeck - 5 000 };
	Intent tweetIntent = new Intent();
	tweetIntent.setType("text/plain");
	final PackageManager packageManager = getPackageManager();
	List<ResolveInfo> list = packageManager.queryIntentActivities(
			tweetIntent, PackageManager.MATCH_DEFAULT_ONLY);

	for (int i = 0; i < twitterApps.length; i++) {
		for (ResolveInfo resolveInfo : list) {
			String p = resolveInfo.activityInfo.packageName;
			if (p != null && p.startsWith(twitterApps[i])) {
				tweetIntent.setPackage(p);
				return tweetIntent;
			}
		}
	}

	return null;
}

Facebook will be similar using "com.facebook.katana", although you still can't set the message text (deprecated July 2011).

Code source: Intent to open twitter client on Android

Solution 4 - Android

Try this one for sharing only three apps-Facebook, Twitter, KakaoStory.

public void onShareClick(View v){
	List<Intent> targetShareIntents=new ArrayList<Intent>();
	Intent shareIntent=new Intent();
	shareIntent.setAction(Intent.ACTION_SEND);
	shareIntent.setType("text/plain");
	List<ResolveInfo> resInfos=getPackageManager().queryIntentActivities(shareIntent, 0);
	if(!resInfos.isEmpty()){
		System.out.println("Have package");
		for(ResolveInfo resInfo : resInfos){
			String packageName=resInfo.activityInfo.packageName;
			Log.i("Package Name", packageName);
			if(packageName.contains("com.twitter.android") || packageName.contains("com.facebook.katana") || packageName.contains("com.kakao.story")){
				Intent intent=new Intent();
				intent.setComponent(new ComponentName(packageName, resInfo.activityInfo.name));
				intent.setAction(Intent.ACTION_SEND);
				intent.setType("text/plain");
				intent.putExtra(Intent.EXTRA_TEXT, "Text");
				intent.putExtra(Intent.EXTRA_SUBJECT, "Subject");
				intent.setPackage(packageName);
				targetShareIntents.add(intent);
			}
		}
		if(!targetShareIntents.isEmpty()){
			System.out.println("Have Intent");
			Intent chooserIntent=Intent.createChooser(targetShareIntents.remove(0), "Choose app to share");
			chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetShareIntents.toArray(new Parcelable[]{}));
			startActivity(chooserIntent);
		}else{
			System.out.println("Do not Have Intent");
			showDialaog(this);
		}
	}
}

Solution 5 - Android

Thanks to @dacoinminster. I make some modifications to his answer including package names of the popular apps and sorting of those apps.

List<Intent> targetShareIntents = new ArrayList<Intent>();
Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
PackageManager pm = getActivity().getPackageManager();
List<ResolveInfo> resInfos = pm.queryIntentActivities(shareIntent, 0);
if (!resInfos.isEmpty()) {
    System.out.println("Have package");
    for (ResolveInfo resInfo : resInfos) {
        String packageName = resInfo.activityInfo.packageName;
        Log.i("Package Name", packageName);

        if (packageName.contains("com.twitter.android") || packageName.contains("com.facebook.katana")
                || packageName.contains("com.whatsapp") || packageName.contains("com.google.android.apps.plus")
                || packageName.contains("com.google.android.talk") || packageName.contains("com.slack")
                || packageName.contains("com.google.android.gm") || packageName.contains("com.facebook.orca")
                || packageName.contains("com.yahoo.mobile") || packageName.contains("com.skype.raider")
                || packageName.contains("com.android.mms")|| packageName.contains("com.linkedin.android")
                || packageName.contains("com.google.android.apps.messaging")) {
            Intent intent = new Intent();

            intent.setComponent(new ComponentName(packageName, resInfo.activityInfo.name));
            intent.putExtra("AppName", resInfo.loadLabel(pm).toString());
            intent.setAction(Intent.ACTION_SEND);
            intent.setType("text/plain");
            intent.putExtra(Intent.EXTRA_TEXT, "https://website.com/");
            intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.share_text));
            intent.setPackage(packageName);
            targetShareIntents.add(intent);
        }
    }
    if (!targetShareIntents.isEmpty()) {
        Collections.sort(targetShareIntents, new Comparator<Intent>() {
            @Override
            public int compare(Intent o1, Intent o2) {
                return o1.getStringExtra("AppName").compareTo(o2.getStringExtra("AppName"));
            }
        });
        Intent chooserIntent = Intent.createChooser(targetShareIntents.remove(0), "Select app to share");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetShareIntents.toArray(new Parcelable[]{}));
        startActivity(chooserIntent);
    } else {
        Toast.makeText(getActivity(), "No app to share.", Toast.LENGTH_LONG).show();
    }
}

Solution 6 - Android

You can try the code below, it works perfectly.

Here we share to some specific apps, that are Facebook, Messenger, Twitter, Google Plus and Gmail.

public void shareIntentSpecificApps() {
        List<Intent> intentShareList = new ArrayList<Intent>();
        Intent shareIntent = new Intent();
        shareIntent.setAction(Intent.ACTION_SEND);
        shareIntent.setType("text/plain");
        List<ResolveInfo> resolveInfoList = getPackageManager().queryIntentActivities(shareIntent, 0);

        for (ResolveInfo resInfo : resolveInfoList) {
            String packageName = resInfo.activityInfo.packageName;
            String name = resInfo.activityInfo.name;
            Log.d(TAG, "Package Name : " + packageName);
            Log.d(TAG, "Name : " + name);

            if (packageName.contains("com.facebook") ||
                    packageName.contains("com.twitter.android") ||
                    packageName.contains("com.google.android.apps.plus") ||
                    packageName.contains("com.google.android.gm")) {

                if (name.contains("com.twitter.android.DMActivity")) {
                    continue;
                }

                Intent intent = new Intent();
                intent.setComponent(new ComponentName(packageName, name));
                intent.setAction(Intent.ACTION_SEND);
                intent.setType("text/plain");
                intent.putExtra(Intent.EXTRA_SUBJECT, "Your Subject");
                intent.putExtra(Intent.EXTRA_TEXT, "Your Content");
                intentShareList.add(intent);
            }
        }

        if (intentShareList.isEmpty()) {
            Toast.makeText(MainActivity.this, "No apps to share !", Toast.LENGTH_SHORT).show();
        } else {
            Intent chooserIntent = Intent.createChooser(intentShareList.remove(0), "Share via");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentShareList.toArray(new Parcelable[]{}));
            startActivity(chooserIntent);
        }
    }

Solution 7 - Android

This solution shows a list of applications in a ListView dialog that resembles the chooser:

screenshot

It is up to you to:

  1. obtain the list of relevant application packages
  2. given a package name, invoke the relevant intent

The adapter class:

import java.util.List;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class ChooserArrayAdapter extends ArrayAdapter<String> {
	PackageManager mPm;
	int mTextViewResourceId;
	List<String> mPackages;

	public ChooserArrayAdapter(Context context, int resource, int textViewResourceId, List<String> packages) {
		super(context, resource, textViewResourceId, packages);
		mPm = context.getPackageManager();
		mTextViewResourceId = textViewResourceId;
		mPackages = packages;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
	    String pkg = mPackages.get(position);
	    View view = super.getView(position, convertView, parent);

	    try {
			ApplicationInfo ai = mPm.getApplicationInfo(pkg, 0);

			CharSequence appName = mPm.getApplicationLabel(ai);
			Drawable appIcon = mPm.getApplicationIcon(pkg);
		
		    TextView textView = (TextView) view.findViewById(mTextViewResourceId);
		    textView.setText(appName);
		    textView.setCompoundDrawablesWithIntrinsicBounds(appIcon, null, null, null);
		    textView.setCompoundDrawablePadding((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 12, getContext().getResources().getDisplayMetrics()));
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}

	    return view;
	}

}

and its usage:

	void doXxxButton() {
		final List<String> packages = ...;
		if (packages.size() > 1) {
			ArrayAdapter<String> adapter = new ChooserArrayAdapter(MyActivity.this, android.R.layout.select_dialog_item, android.R.id.text1, packages);

			new AlertDialog.Builder(MyActivity.this)
			.setTitle(R.string.app_list_title)
            .setAdapter(adapter, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int item ) {
                    invokeApplication(packages.get(item));
                }
            })
            .show();
		} else if (packages.size() == 1) {
            invokeApplication(packages.get(0));
		}
	}

	void invokeApplication(String packageName) {
		// given a package name, create an intent and fill it with data
		...
		startActivityForResult(intent, rq);
	}

Solution 8 - Android

The cleanest way is to copy the following classes: ShareActionProvider, ActivityChooserView, ActivityChooserModel. Add the ability to filter the intents in the ActivityChooserModel, and the appropriate support methods in the ShareActionProvider. I created the necessary classes, you can copy them into your project (https://gist.github.com/saulpower/10557956). This not only adds the ability to filter the apps you would like to share with (if you know the package name), but also to turn off history.

private final String[] INTENT_FILTER = new String[] {
    "com.twitter.android",
    "com.facebook.katana"
};

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.journal_entry_menu, menu);

    // Set up ShareActionProvider's default share intent
    MenuItem shareItem = menu.findItem(R.id.action_share);

    if (shareItem instanceof SupportMenuItem) {
        mShareActionProvider = new ShareActionProvider(this);
        mShareActionProvider.setShareIntent(ShareUtils.share(mJournalEntry));
        mShareActionProvider.setIntentFilter(Arrays.asList(INTENT_FILTER));
        mShareActionProvider.setShowHistory(false);
        ((SupportMenuItem) shareItem).setSupportActionProvider(mShareActionProvider);
    }

    return super.onCreateOptionsMenu(menu);
}

Solution 9 - Android

I have improved @dacoinminster answer and this is the result with an example to share your app:

// Intents with SEND action
PackageManager packageManager = context.getPackageManager();
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.setType("text/plain");
List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(sendIntent, 0);

List<LabeledIntent> intentList = new ArrayList<LabeledIntent>();
Resources resources = context.getResources();

for (int j = 0; j < resolveInfoList.size(); j++) {
	ResolveInfo resolveInfo = resolveInfoList.get(j);
	String packageName = resolveInfo.activityInfo.packageName;
	Intent intent = new Intent();
	intent.setAction(Intent.ACTION_SEND);
	intent.setComponent(new ComponentName(packageName,
	resolveInfo.activityInfo.name));
	intent.setType("text/plain");

	if (packageName.contains("twitter")) {
		intent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.twitter) + "https://play.google.com/store/apps/details?id=" + context.getPackageName());
	} else {
		// skip android mail and gmail to avoid adding to the list twice
		if (packageName.contains("android.email") || packageName.contains("android.gm")) {
			continue;
		}
		intent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.largeTextForFacebookWhatsapp) + "https://play.google.com/store/apps/details?id=" + context.getPackageName());
	}

	intentList.add(new LabeledIntent(intent, packageName, resolveInfo.loadLabel(packageManager), resolveInfo.icon));
}

Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:"));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, resources.getString(R.string.subjectForMailApps));
emailIntent.putExtra(Intent.EXTRA_TEXT, resources.getString(R.string.largeTextForMailApps) + "https://play.google.com/store/apps/details?id=" + context.getPackageName());

context.startActivity(Intent.createChooser(emailIntent, resources.getString(R.string.compartirEn)).putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toArray(new LabeledIntent[intentList.size()])));

Solution 10 - Android

Intent emailIntent = new Intent(Intent.ACTION_SENDTO, 
    Uri.fromParts("mailto", "[email protected]", null));
emailIntent.putExtra(Intent.EXTRA_SUBJECT, text);
startActivity(Intent.createChooser(emailIntent, "Send email..."));

Solution 11 - Android

I had same problem and this accepted solution didn't helped me, if someone has same problem you can use my code snippet:

// example of filtering and sharing multiple images with texts
// remove facebook from sharing intents
private void shareFilter(){

    String share = getShareTexts();
    ArrayList<Uri> uris = getImageUris();

    List<Intent> targets = new ArrayList<>();
    Intent template = new Intent(Intent.ACTION_SEND_MULTIPLE);
    template.setType("image/*");
    List<ResolveInfo> candidates = getActivity().getPackageManager().
            queryIntentActivities(template, 0);

    // remove facebook which has a broken share intent
    for (ResolveInfo candidate : candidates) {
        String packageName = candidate.activityInfo.packageName;
        if (!packageName.equals("com.facebook.katana")) {
            Intent target = new Intent(Intent.ACTION_SEND_MULTIPLE);
            target.setType("image/*");
            target.putParcelableArrayListExtra(Intent.EXTRA_STREAM,uris);
            target.putExtra(Intent.EXTRA_TEXT, share);
            target.setPackage(packageName);
            targets.add(target);
        }
    }
    Intent chooser = Intent.createChooser(targets.remove(0), "Share Via");
    chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targets.toArray(new Parcelable[targets.size()]));
    startActivity(chooser);

}

Solution 12 - Android

So simple and concise. Thanks to the Open source developer, cketti for sharing this solution:

String mailto = "mailto:[email protected]" +
    "?cc=" + "[email protected]" +
    "&subject=" + Uri.encode(subject) +
    "&body=" + Uri.encode(bodyText);

Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
emailIntent.setData(Uri.parse(mailto));

try {
  startActivity(emailIntent);
} catch (ActivityNotFoundException e) {
  //TODO: Handle case where no email app is available
}

And this is the link to his/her gist.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKyle CleggView Question on Stackoverflow
Solution 1 - AndroiddacoinminsterView Answer on Stackoverflow
Solution 2 - AndroidSavvas DalkitsisView Answer on Stackoverflow
Solution 3 - AndroidKyle CleggView Answer on Stackoverflow
Solution 4 - Androiduser3098538View Answer on Stackoverflow
Solution 5 - AndroidOguz OzcanView Answer on Stackoverflow
Solution 6 - AndroidDang NguyenView Answer on Stackoverflow
Solution 7 - Android18446744073709551615View Answer on Stackoverflow
Solution 8 - AndroidsaulpowerView Answer on Stackoverflow
Solution 9 - Androiduser2528167View Answer on Stackoverflow
Solution 10 - AndroidmuruganView Answer on Stackoverflow
Solution 11 - AndroidJemo MgebrishviliView Answer on Stackoverflow
Solution 12 - Androiduser10496632View Answer on Stackoverflow