Как фильтровать определенные приложения для намерения ACTION_SEND (и устанавливать другой текст для каждого приложения)

Как вы можете отфильтровать конкретные приложения при использовании намерений ACTION_SEND? Этот вопрос задавался различными способами, но я не смог собрать решение на основе полученных ответов. Надеюсь, кто-то может помочь. Я хотел бы предоставить возможность совместного использования в приложении. Следуя рекомендациям Android Dev Alexander Lucas, я бы предпочел сделать это с использованием намерений и не использовать API Facebook/Twitter.

Sharing using ACTION_SEND intent

Совместное использование с использованием намерения ACTION_SEND отличное, но проблема (1) Я не хочу, чтобы каждый параметр обмена там, я бы предпочел ограничить его FB, Twitter и электронной почтой, и (2) я не хотите делиться одним и тем же приложением для совместного использования. Например, в моем блоге Twitter я собираюсь включить некоторые упоминания, а хэштеги ограничили это до 140 символов или меньше, в то время как общий ресурс facebook будет включать ссылку и изображение функции.

Можно ли ограничить варианты намерения ACTION_SEND (share)? Я видел что-то об использовании PackageManager и queryIntentActivities, но не смог выяснить связь между PackageManager и намерением ACTION_SEND.

ИЛИ

Вместо того, чтобы фильтровать приложения для обмена, моя проблема также может быть решена, если я смогу использовать намерение ACTION_SEND перейти непосредственно на facebook или твиттер, а не появляться в диалоговом окне. Если бы это было так, тогда я мог бы создать свой собственный диалог, и когда они нажмут "Facebook", создайте намерение Facebook и просто отправят их полностью в Facebook. То же самое с Twitter.

ИЛИ это невозможно? Являются ли API Facebook и Twitter единственным способом?

Ответ 1

Насколько мне известно, у StackOverflow есть много людей, которые задают этот вопрос по-разному, но никто еще не ответил на него полностью.

Моя спецификация призвала пользователя выбирать электронную почту, твиттер, facebook или SMS, с пользовательским текстом для каждого. Вот как я это сделал:

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);       
}

Я нашел несколько способов сделать это в разных местах, но я не видел его нигде в другом месте.

Обратите внимание, что этот метод также скрывает все глупые опции, которые я не хочу, например, разделение Wi-Fi и Bluetooth.

Надеюсь, это поможет кому-то.

Изменить: в комментарии мне было предложено объяснить, что делает этот код. В принципе, он создает намерение ACTION_SEND для собственного почтового клиента ТОЛЬКО, а затем использует другие намерения для выбора. Создание оригинального намерения, связанного с электронной почтой, избавляет от лишнего мусора, такого как Wi-Fi и Bluetooth, а затем я получаю другие намерения, которые я хочу получить от общего ACTION_SEND текста ACTION_SEND типа, и применяю их, прежде чем показывать выбор.

Когда я хватаю дополнительные намерения, я устанавливаю для каждого пользовательский текст.

Edit2: Прошло некоторое время с тех пор, как я опубликовал это, и все немного изменилось. Если вы видите gmail дважды в списке параметров, попробуйте удалить специальную обработку для "android.gm", как указано в комментарии от @h_k ниже.

Поскольку этот ответ является источником почти всех моих точек репутации stackoverflow, я должен хотя бы попытаться сохранить его в актуальном состоянии.

Ответ 2

Если вам нужна настраиваемая опция, вы не должны полагаться на диалоговое окно по умолчанию, предоставленное андроидом для этого действия.

То, что вам нужно сделать, это развернуть свой собственный. Вам нужно будет запросить PackageManager, в котором пакеты обрабатывают требуемое действие, а затем на основе ответа вы применяете фильтрацию и настраиваемый текст.

В частности, взгляните на метод queryIntentActivities PackageManager. Вы создаете намерение, которое запустило диалог по умолчанию (намерение ACTION_SEND), передать это этому методу, и вы получите список объектов, содержащих информацию о действиях, которые могут обрабатывать это намерение. Используя это, вы можете выбрать те, которые вы хотите.

После того, как вы создадите список пакетов, которые вы хотите представить, вам нужно создать собственный диалог со списком (предпочтительно действие с темой диалога), которое отобразит этот список.

Следует отметить, однако, что очень сложно сделать этот пользовательский диалог похожим на стандартный. Проблема в том, что тема, используемая в этом диалоговом окне, является внутренней темой и не может использоваться вашим приложением. Вы можете либо попытаться сделать его похожим на родной, либо вы хотите, чтобы он выглядел совершенно по-своему (многие приложения делают это как приложение галереи и т.д.).

Ответ 3

Нашел решение, которое работает для меня, смотрящего здесь (см. Третий комментарий к первому ответу). Этот код ищет действительный клиент Twitter и использует его для публикации твита. Примечание. Это не дает вам намерения с различными клиентами Twitter и позволяет вам выбирать.

Поделиться с помощью twitter:

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

Вызов этого метода:

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 будет похож на " com.facebook.katana ", хотя вы по-прежнему не можете установить текст сообщения (устаревший июль 2011 г.).

Источник кода: намерение открыть клиент Twitter на Android

Ответ 4

Попробуйте использовать только три приложения - 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);
        }
    }
}

Ответ 5

Это решение отображает список приложений в диалоговом окне ListView, который похож на выбор:

screenshot

Это зависит от вас:

  • получить список соответствующих пакетов приложений
  • с именем пакета, вызовите соответствующее намерение

Класс адаптера:

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;
    }

}

и его использование:

    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);
    }

Ответ 6

Вы можете попробовать код ниже, он отлично работает.

Здесь мы поделимся некоторыми конкретными приложениями, такими как Facebook, Messenger, Twitter, Google Plus и 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);
        }
    }

Ответ 7

Благодаря @dacoinminster. Я вношу некоторые изменения в его ответ, включая имена пакетов популярных приложений и сортировку этих приложений.

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();
    }
}

Ответ 8

Самый чистый способ - скопировать следующие классы: ShareActionProvider, ActivityChooserView, ActivityChooserModel. Добавьте способность фильтровать намерения в ActivityChooserModel и соответствующие методы поддержки в ShareActionProvider. Я создал необходимые классы, вы можете скопировать их в свой проект (https://gist.github.com/saulpower/10557956). Это не только добавляет возможность фильтровать приложения, с которыми вы хотите поделиться (если вы знаете имя пакета), но и отключить историю.

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 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);
}

Ответ 9

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..."));

Ответ 10

У меня улучшен ответ @dacoinminster, и это результат с примером для совместного использования вашего приложения:

// 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()])));

Ответ 11

У меня была такая же проблема, и это принятое решение мне не помогло, если у кого-то есть такая же проблема, вы можете использовать мой фрагмент кода:

// 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);

}