60

I'd like to leverage the built-in intent chooser to display a custom filtered list of apps for user to select from and launch.

I know how to get a list of installed packages:

final Intent myIntent = new Intent(android.content.Intent.ACTION_MAIN);  
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(myIntent, 0);

At this point I want to filter the list based on a specific string (or variation of strings) contained within the package name, which I can figure out how to do as well.

But here's where I get stuck. As far as I know, Intent.createChooser() takes only a single target Intent as a parameter. I was hoping there was an overload that took a list of intents based on package and class names or something. But I don't see anything like that. Did I miss that somewhere?

So the question is, is this possible to do with a built-in chooser, or do I have to construct my own with AlertDialog Builder? I'm hoping to avoid the later.

Thanks in advance.

Kon
  • 27,113
  • 11
  • 60
  • 86

6 Answers6

112

Here is a solution i whipped up. I use it to have different intent data for each selection in the chooser, but you can easily remove an intent from the list as well.

List<Intent> targetedShareIntents = new ArrayList<Intent>();
Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND);
shareIntent.setType("text/plain");
List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(shareIntent, 0);
if (!resInfo.isEmpty()) {
    for (ResolveInfo resolveInfo : resInfo) {
        String packageName = resolveInfo.activityInfo.packageName;
        Intent targetedShareIntent = new Intent(android.content.Intent.ACTION_SEND);
        targetedShareIntent.setType("text/plain");
        targetedShareIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, "subject to be shared");
        if (TextUtils.equals(packageName, "com.facebook.katana")) {
            targetedShareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "http://link-to-be-shared.com");
        } else {
            targetedShareIntent.putExtra(android.content.Intent.EXTRA_TEXT, "text message to shared");
        }
        targetedShareIntent.setPackage(packageName);
        targetedShareIntents.add(targetedShareIntent);
    }
    Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(0), "Select app to share");
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedShareIntents.toArray(new Parcelable[targetedShareIntents.size()]));
    startActivity(chooserIntent);
}

EDIT

While using this method i get names of some apps as android system. If anybody get this error pls add below lines before targetedShareIntents.add(targetedShareIntent);

 targetedShareIntent.setClassName(
                        resolveInfo.activityInfo.packageName,
                        resolveInfo.activityInfo.name);

source : Android share intent Twitter: How to share only by Tweet or Direct Message?

starball
  • 20,030
  • 7
  • 43
  • 238
gumbercules
  • 1,129
  • 2
  • 7
  • 2
  • 9
    thanks, and a little tip: to preserve applications order from `getPackageManager().queryIntentActivities(shareIntent, 0);`, call `Intent chooserIntent = Intent.createChooser(targetedShareIntents.remove(targetedShareIntents.size()-1), "Select app to share");`, chooser adds its initializing intent to the end of extra_initial_intents :) – Berťák Aug 30 '12 at 09:34
  • 1
    gumbercules, I tried the above code, but the message doesn't appear on the post. It shows me a facebook page with empty message... – Ramesh Sangili Feb 13 '13 at 20:02
  • @TomDignan I've tested it and it works. If you want to remove an intent, don't add it to the targetedShareIntents. – cjc343 Mar 19 '13 at 20:07
  • 1
    did some more research and it is important to use `targetedShareIntent.setPackage(packageName)` (http://developer.android.com/reference/android/content/Intent.html#getPackage()) to only allow intents from a given package (that would be one selected from the targetedShareIntents. Afterwards custom intents are appended. To improve this code even more, specifyfurther with `targetedShareIntent.setClassName(packageName,className)` – Makibo May 22 '13 at 05:19
  • 8
    I created a gist for a reusable solution based on this answer https://gist.github.com/mediavrog/5625602 – Makibo May 22 '13 at 06:26
  • @Makibo Thanks! I will try it again soon. For now I'll delete my old comment. – Thomas Dignan May 23 '13 at 05:32
  • 2
    Im not getting/importing StringUtils @gumbercules – Crishnan Kandada Jan 03 '14 at 11:10
  • 1
    @Makibo I did the same way to prevent further chooser-type intents. But it seems only can get the app name, not the activity name. If there are two activities that handled the type of intent in the same app. Only icons are different. The labels are the same. Do you have any idea to fix this? Thanks in advance. – shiami May 17 '15 at 17:39
  • The above code is not working for Samsung Galaxy deivces, using an attachment to send via Whatsapp and Gmail, Whatsapp says "Sharing failed, please try again", Gmail says "Unable to attach file". But same works in Redmi and Other devices. Checked permissions are given for Files and Media, in the calling app., Whatsapp and Gmail also have media access permissions. Samsung Galaxy mobile still shows: FileNotFoundException open failed: ENOENT (No such file or directory) – Deepak J Aug 10 '22 at 06:30
26

The only one additional parameter for the chooser is Intent.EXTRA_INITIAL_INTENTS. Its description is:

A Parcelable[] of Intent or LabeledIntent objects as set with putExtra(String, Parcelable[]) of additional activities to place a the front of the list of choices, when shown to the user with a ACTION_CHOOSER.

I haven't found any way in Android sources to exclude other activities from the list, so it seems there's no way to do what you want to do using the chooser.

EDIT: That's really easy to find out. Just check ChooserActivity and ResolverActivity source code. These classes are rather small.

ZINE Mahmoud
  • 1,272
  • 1
  • 17
  • 32
Michael
  • 53,859
  • 22
  • 133
  • 139
  • It seems that what I was looking for is not possible, so you were closest (and only). – Kon Apr 26 '11 at 00:52
  • This is the correct answer. Having tried both and reading the docs it is definitely not possible to remove items from the list. – Thomas Dignan Feb 12 '13 at 08:23
  • Correct. I have tried every way but no result. Finally, I have to customize my own "createChooser()" method just like Intent.createChooser(). – cmoaciopm Sep 14 '13 at 06:39
  • 1
    custom solution this link -> http://stackoverflow.com/questions/9730243/android-how-to-filter-specific-apps-for-action-send-intent – deadfish Jul 14 '14 at 11:45
7

I did an small modification to have a list of the apps that you want to share with by name. It is almost what you already posted but adding the apps to share by name

String[] nameOfAppsToShareWith = new String[] { "facebook", "twitter", "gmail" };
String[] blacklist = new String[]{"com.any.package", "net.other.package"};
// your share intent
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "some text");
intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "a subject");
// ... anything else you want to add invoke custom chooser
startActivity(generateCustomChooserIntent(intent, blacklist));

private Intent generateCustomChooserIntent(Intent prototype,
            String[] forbiddenChoices)
    {
        List<Intent> targetedShareIntents = new ArrayList<Intent>();
        List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
        Intent chooserIntent;

        Intent dummy = new Intent(prototype.getAction());
        dummy.setType(prototype.getType());
        List<ResolveInfo> resInfo = getPackageManager().queryIntentActivities(dummy,0);

        if (!resInfo.isEmpty())
        {
            for (ResolveInfo resolveInfo : resInfo)
            {
                if (resolveInfo.activityInfo == null
                        || Arrays.asList(forbiddenChoices).contains(
                                resolveInfo.activityInfo.packageName))
                    continue;
                //Get all the posible sharers
                HashMap<String, String> info = new HashMap<String, String>();
                info.put("packageName", resolveInfo.activityInfo.packageName);
                info.put("className", resolveInfo.activityInfo.name);
                String appName = String.valueOf(resolveInfo.activityInfo
                        .loadLabel(getPackageManager()));
                info.put("simpleName", appName);
                //Add only what we want
                if (Arrays.asList(nameOfAppsToShareWith).contains(
                        appName.toLowerCase()))
                {
                    intentMetaInfo.add(info);
                }
            }

            if (!intentMetaInfo.isEmpty())
            {
                // sorting for nice readability
                Collections.sort(intentMetaInfo,
                        new Comparator<HashMap<String, String>>()
                        {
                            @Override public int compare(
                                    HashMap<String, String> map,
                                    HashMap<String, String> map2)
                            {
                                return map.get("simpleName").compareTo(
                                        map2.get("simpleName"));
                            }
                        });

                // create the custom intent list
                for (HashMap<String, String> metaInfo : intentMetaInfo)
                {
                    Intent targetedShareIntent = (Intent) prototype.clone();
                    targetedShareIntent.setPackage(metaInfo.get("packageName"));
                    targetedShareIntent.setClassName(
                            metaInfo.get("packageName"),
                            metaInfo.get("className"));
                    targetedShareIntents.add(targetedShareIntent);
                }
                String shareVia = getString(R.string.offer_share_via);
                String shareTitle = shareVia.substring(0, 1).toUpperCase()
                        + shareVia.substring(1);
                chooserIntent = Intent.createChooser(targetedShareIntents
                        .remove(targetedShareIntents.size() - 1), shareTitle);
                chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,
                        targetedShareIntents.toArray(new Parcelable[] {}));
                return chooserIntent;
            }
        }

        return Intent.createChooser(prototype,
                getString(R.string.offer_share_via));
    }

It almost the same solution that Makibo posted but with a little add to make an easy form of picking the apps you want to share with just by adding the name so you won't have any problem in case they change the package name or something like this. As long as they don't change the name.

pleonasmik
  • 779
  • 10
  • 16
  • 1
    This works, but for some reason, on Android 6, it can show empty name for apps (like of "Gogobot"), and also cause empty cells in the grid of items that are shown (in my case of Nexus 5, an empty cell at the end of the one-before-last-line of the grid). – android developer Mar 07 '16 at 09:26
6

My implementation of custom open chooser.

Features:

  • apps are sorted in alphabetical order
  • handling default app defined by user
  • handling no apps case
  • filtering itself

public static Intent createOpenFileIntent(Context context, String pathToFile) {
    File file = new File(pathToFile);
    String extension = extensionFromName(file.getName());
    String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
    if (mimeType == null) {
        //If android doesn't know extension we can check our own list.
        mimeType = KNOWN_MIME_TYPES.get(DataViewHelper.extensionFromName(file.getName()));
    }

    Intent openIntent = new Intent();
    openIntent.setAction(android.content.Intent.ACTION_VIEW);
    openIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    openIntent.setDataAndType(Uri.fromFile(file), mimeType);

    // 1. Check if there is a default app opener for this type of content.
    final PackageManager packageManager = context.getPackageManager();
    ResolveInfo defaultAppInfo = packageManager.resolveActivity(openIntent, PackageManager.MATCH_DEFAULT_ONLY);
    if (!defaultAppInfo.activityInfo.name.endsWith("ResolverActivity")) {
        return openIntent;
    }

    // 2. Retrieve all apps for our intent. If there are no apps - return usual already created intent.
    List<Intent> targetedOpenIntents = new ArrayList<Intent>();
    List<ResolveInfo> appInfoList = packageManager.queryIntentActivities(openIntent, PackageManager.MATCH_DEFAULT_ONLY);
    if (appInfoList.isEmpty()) {
        return openIntent;
    }

    // 3. Sort in alphabetical order, filter itself and create intent with the rest of the apps.
    Collections.sort(appInfoList, new Comparator<ResolveInfo>() {
        @Override
        public int compare(ResolveInfo first, ResolveInfo second) {
            String firstName = packageManager.getApplicationLabel(first.activityInfo.applicationInfo).toString();
            String secondName = packageManager.getApplicationLabel(second.activityInfo.applicationInfo).toString();
            return firstName.compareToIgnoreCase(secondName);
        }
    });
    for (ResolveInfo appInfo : appInfoList) {
        String packageName = appInfo.activityInfo.packageName;
        if (packageName.equals(context.getPackageName())) {
            continue;
        }

        Intent targetedOpenIntent = new Intent(android.content.Intent.ACTION_VIEW)
                .setDataAndType(Uri.fromFile(file), mimeType)
                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                .setPackage(packageName);
        targetedOpenIntents.add(targetedOpenIntent);
    }
    Intent chooserIntent = Intent.createChooser(targetedOpenIntents.remove(targetedOpenIntents.size() - 1), context.getString(R.string.context_menu_open_in))
            .putExtra(Intent.EXTRA_INITIAL_INTENTS, targetedOpenIntents.toArray(new Parcelable[] {}));

    return chooserIntent;
}

public static String extensionFromName(String fileName) {
    int dotPosition = fileName.lastIndexOf('.');

    // If extension not present or empty
    if (dotPosition == -1 || dotPosition == fileName.length() - 1) {
        return "";
    } else {
        return fileName.substring(dotPosition + 1).toLowerCase(Locale.getDefault());
    }
}
oxied
  • 1,773
  • 19
  • 14
  • 3
    To get the same label as the one displayed by the action chooser, you should use `ResolveInfo.loadLabel(packageManager)` instead of `packageManager.getApplicationLabel(ResolveInfo.activityInfo.applicationInfo)`. The result is not always the same. (This is used in the override of `compare()` when sorting in Step 3). For example, for Dropbox, loadLabel() will return "Add to Dropbox" and getApplicationLabel() will return "Dropbox". – Pooks Mar 19 '14 at 08:10
1

I was attempting to do the same thing but with a ShareActionProvider. The method in gumbercules's post didn't work well with the ShareActionProvider so I copied and modified the ShareActionProvider related classes to allow custom filtering of the ShareActionProvider suggestions.

The only change is to the ActivityChooserModel.loadActivitiesIfNeeded() method. In my case, I wanted to filter out the YouTube package.

public static final String YOUTUBE_PACKAGE = "com.google.android.youtube";

private boolean loadActivitiesIfNeeded() {
    if (mReloadActivities && mIntent != null) {
        mReloadActivities = false;
        mActivities.clear();
        List<ResolveInfo> resolveInfos = mContext.getPackageManager()
                .queryIntentActivities(mIntent, 0);
        final int resolveInfoCount = resolveInfos.size();
        for (int i = 0; i < resolveInfoCount; i++) {
            ResolveInfo resolveInfo = resolveInfos.get(i);
            // Filter out the YouTube package from the suggestions list
            if (!resolveInfo.activityInfo.packageName.equals(YOUTUBE_PACKAGE)) {
                mActivities.add(new ActivityResolveInfo(resolveInfo));
            }
        }
        return true;
    }
    return false;
}

Code at https://github.com/danghiskhan/FilteredShareActionProvider

danghis khan
  • 176
  • 2
  • 6
1

This solution is based on this post https://rogerkeays.com/how-to-remove-the-facebook-android-sharing-intent

// get available share intents
final String packageToBeFiltered = "com.example.com"
List<Intent> targets = new ArrayList<Intent>();
Intent template = new Intent(Intent.ACTION_SEND);
template.setType("text/plain");
List<ResolveInfo> candidates = this.getPackageManager().queryIntentActivities(template, 0);

// filter package here
for (ResolveInfo candidate : candidates) {
    String packageName = candidate.activityInfo.packageName;
    if (!packageName.equals(packageToBeFiltered)) {
    Intent target = new Intent(android.content.Intent.ACTION_SEND);
    target.setType("text/plain");
    target.putExtra(Intent.EXTRA_TEXT, "Text to share"));
    target.setPackage(packageName);
    targets.add(target);
   }
}
if (!targets.isEmpty()) {
Intent chooser = Intent.createChooser(targets.get(0), "Share dialog title goes here"));
chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targets.toArray(new Parcelable[]{}));
startActivity(chooser);
}
Suraj
  • 609
  • 6
  • 13