Custom filtering of intent chooser based on installed Android package name
Asked Answered
C

7

61

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.

Climactic answered 20/4, 2011 at 18:6 Comment(0)
M
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.

Monofilament answered 20/4, 2011 at 19:12 Comment(4)
It seems that what I was looking for is not possible, so you were closest (and only).Climactic
This is the correct answer. Having tried both and reading the docs it is definitely not possible to remove items from the list.Periclean
Correct. I have tried every way but no result. Finally, I have to customize my own "createChooser()" method just like Intent.createChooser().Accident
custom solution this link -> #9730743Glottalized
D
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?

Donaghue answered 18/12, 2011 at 5:38 Comment(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 :)Thrice
gumbercules, I tried the above code, but the message doesn't appear on the post. It shows me a facebook page with empty message...Melise
@TomDignan I've tested it and it works. If you want to remove an intent, don't add it to the targetedShareIntents.Diane
did some more research and it is important to use targetedShareIntent.setPackage(packageName) (developer.android.com/reference/android/content/…) 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)Epigoni
I created a gist for a reusable solution based on this answer gist.github.com/mediavrog/5625602Epigoni
@Epigoni Thanks! I will try it again soon. For now I'll delete my old comment.Periclean
Im not getting/importing StringUtils @DonaghueUnleash
@Epigoni 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.Halo
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)Organography
M
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.

Monofilament answered 20/4, 2011 at 19:12 Comment(4)
It seems that what I was looking for is not possible, so you were closest (and only).Climactic
This is the correct answer. Having tried both and reading the docs it is definitely not possible to remove items from the list.Periclean
Correct. I have tried every way but no result. Finally, I have to customize my own "createChooser()" method just like Intent.createChooser().Accident
custom solution this link -> #9730743Glottalized
C
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.

Cauvery answered 6/1, 2014 at 16:12 Comment(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).Hotchpotch
S
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());
    }
}
Sturges answered 16/9, 2013 at 19:35 Comment(1)
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".Saavedra
A
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

Affirmation answered 15/1, 2014 at 1:37 Comment(0)
G
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);
}
Gilgai answered 10/10, 2017 at 11:4 Comment(0)
P
0

For Kotlin users this code worked for me if you are targeting API 24 you should use Intent.EXTRA_EXCLUDE_COMPONENTS instead of Intent.EXTRA_INITIAL_INTENTS which is ignored by default

    @RequiresApi(Build.VERSION_CODES.N)
private fun goToEmailFilteredApps(email: String) {

    val intent = Intent(Intent.ACTION_SEND)
    intent.setTypeAndNormalize("message/rfc822")
    intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))

    val chooser = Intent.createChooser(intent, "Send email")

  val emailAppsPackageNames = arrayListOf(
        "com.google.android.gm",                // Gmail
        "com.microsoft.office.outlook",         // Outlook
        "com.yahoo.mobile.client.android.mail", // Yahoo Mail
        "pl.onet.poczta",                       // Onet Mail
        "pl.interia.poczta_next",               // Interia Mail
        "pl.wp.pocztao2",                       // o2 Mail
        "pl.wp.wppoczta",                       // WP  Mail
    )
    val targets = ArrayList<ComponentName>()
    try {
        val queries = requireActivity().packageManager.queryIntentActivities(intent, 0)
        for (candidate in queries) {
            val packageName = candidate.activityInfo.packageName
            if (!emailAppsPackageNames.contains(packageName.lowercase())) {
                targets.add(ComponentName(packageName, candidate.activityInfo.name))
            }
        }
        chooser.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, targets.toTypedArray())
        startActivity(chooser)
    } catch (_: ActivityNotFoundException) {
    }
}

Here is the result enter image description here

Prerecord answered 15/11, 2023 at 11:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.