Пользовательская фильтрация выбора намерений на основе имени установленного пакета Android
Я хотел бы использовать встроенный инструмент выбора намерений для отображения настраиваемого отфильтрованного списка приложений, которые пользователь может выбрать и запустить.
Я знаю, как получить список установленных пакетов:
final Intent myIntent = new Intent(android.content.Intent.ACTION_MAIN);
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(myIntent, 0);
На данный момент я хочу отфильтровать список на основе определенной строки (или вариации строк), содержащейся в имени пакета, что я также могу выяснить, как это сделать.
Но вот где я застреваю. Насколько я знаю, Intent.createChooser()
принимает только один целевой объект в качестве параметра. Я надеялся, что произошла перегрузка, которая взяла список намерений на основе имен пакетов и классов или чего-то еще. Но я не вижу ничего подобного. Я что-то пропустил?
Таким образом, вопрос в том, возможно ли это сделать со встроенным селектором, или я должен создать свой собственный с помощью AlertDialog Builder? Я надеюсь избежать позже.
Заранее спасибо.
7 ответов
Единственный дополнительный параметр для выбора Intent.EXTRA_INITIAL_INTENTS
, Его описание:
Parcelable[] объектов Intent или LabeledIntent, как установлено с помощью putExtra(String, Parcelable[]) дополнительных действий для размещения перед списком выбора, когда отображается пользователю с ACTION_CHOOSER.
В источниках для Android я не нашел способа исключить другие действия из списка, поэтому, похоже, нет способа сделать то, что вы хотите, с помощью средства выбора.
РЕДАКТИРОВАТЬ: Это действительно легко узнать. Просто проверьте исходный код ChooserActivity и ResolverActivity. Эти классы довольно маленькие.
Вот решение, которое я выбрал. Я использую его, чтобы иметь разные данные о намерениях для каждого выбора в окне выбора, но вы также можете легко удалить намерение из списка. Надеюсь это поможет:
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);
}
РЕДАКТИРОВАТЬ
При использовании этого метода я получаю названия некоторых приложений в качестве системы Android. Если кто-нибудь получит эту ошибку, пожалуйста, добавьте ниже строки, прежде чем targetedShareIntents.add(targetedShareIntent);
targetedShareIntent.setClassName(
resolveInfo.activityInfo.packageName,
resolveInfo.activityInfo.name);
источник: Android поделиться намерением Twitter: как поделиться только через Tweet или Direct Message?
Я сделал небольшую модификацию, чтобы получить список приложений, которыми вы хотите поделиться по имени. Это почти то, что вы уже опубликовали, но добавление приложений, чтобы поделиться по имени
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));
}
Это почти то же самое решение, которое выложил Makibo, но с небольшим дополнением для упрощения выбора приложений, которыми вы хотите поделиться, просто добавив название, чтобы у вас не было никаких проблем в случае, если они изменят имя пакета или что-то вроде этот. Пока они не меняют имя.
Моя реализация кастомного открытого выбора.
Особенности:
- приложения отсортированы в алфавитном порядке
- обработка приложения по умолчанию, определенного пользователем
- не обрабатывать ни одного приложения
- сама фильтрация
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());
}
}
Я пытался сделать то же самое, но с ShareActionProvider. Метод в сообщении gumbercules не работал должным образом с ShareActionProvider, поэтому я скопировал и изменил связанные классы ShareActionProvider, чтобы разрешить пользовательскую фильтрацию предложений ShareActionProvider.
Единственным изменением является метод ActivityChooserModel.loadActivitiesIfNeeded(). В моем случае я хотел отфильтровать пакет YouTube.
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;
}
Код на https://github.com/danghiskhan/FilteredShareActionProvider
Это решение основано на этом посте 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);
}
Для пользователей Kotlin этот код сработал, если вы ориентируетесь на API 24, вам следует использоватьIntent.EXTRA_EXCLUDE_COMPONENTS
вместоIntent.EXTRA_INITIAL_INTENTS
который игнорируется по умолчанию
@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) {
}
}