I have a "share" button on my Android application which calls Intent.ACTION_SEND to display the available applications that the user can utilize to share a link. But I'd like for the applications to be displayed in a certain order, let's say, Whatsapp, then Messenger, then Facebook, and then all the other ones indiscriminately.
I've tried a few methods, it kind of works when I filter the apps to just those three, but then if I add the rest of them, either the apps always get displayed in the same order (happens on my Xiaomi), or the whole thing gets messed up and even duplicates some apps (happens on the Motorola I tested on).
This is kind of what I tried to do, just for testing purposes:
Intent sharingIntent = new Intent(Intent.ACTION_SEND);
List<ResolveInfo> resInfo = mActivity.getPackageManager().queryIntentActivities(sharingIntent, 0);
I then performed three for() loops that would each fully iterate through the resInfo list. Each loop would search for a specific app (Whatsapp, then Messenger, then Facebook) and add it to the Chooser. I then added another similar for() method, this time in order to add the remaining apps:
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_TEXT, shareBody);
targetedShareIntent.setPackage(packageName);
if (!packageName.equals("com.whatsapp") ||
!packageName.equals("com.facebook.orca") ||
!packageName.equals("com.facebook.katana")) {
Toast.makeText(mActivity, packageName, Toast.LENGTH_SHORT).show();
targetedShareIntents.add(targetedShareIntent);
}
}
If I don't add this last method, the Chooser will only display the three first apps I added, and they'll even be displayed in different order if I change the order of the for() loops. But when I do add this method, every app will be displayed in the same order as if I had just regularly called the chooser intent.
Is there any way to work around this?
Note: This answer is for idea only. It is not a complete solution.
I have created the custom http intent which excludes certain apps from the chooser. This might be helpful for you. If I got enough time I will create code for your requirement. I am sharing my code which might be helpful for you.
public static void showBrowserIntent(Activity activity, String fileUrl, String[] forbiddenApps) {
String[] blacklist = new String[]{"com.google.android.apps.docs"};
if (forbiddenApps != null) {
blacklist = forbiddenApps;
}
Intent httpIntent = new Intent(Intent.ACTION_VIEW);
httpIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
httpIntent.setData(Uri.parse(fileUrl));
httpIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
List<Intent> targetedShareIntents = new ArrayList<Intent>();
List<HashMap<String, String>> intentMetaInfo = new ArrayList<HashMap<String, String>>();
Intent chooserIntent;
List<ResolveInfo> resInfo = activity.getPackageManager().queryIntentActivities(httpIntent, 0);
Intent chooser = Intent.createChooser(httpIntent, "Choose Downloader/Browser");
if (!resInfo.isEmpty()) {
for (ResolveInfo resolveInfo : resInfo) {
if (resolveInfo.activityInfo == null
|| Arrays.asList(blacklist).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(activity.getPackageManager()));
info.put("simpleName", appName);
//Add only what we want
if (!Arrays.asList(blacklist).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) httpIntent.clone();
targetedShareIntent.setPackage(metaInfo.get("packageName"));
targetedShareIntent.setClassName(
metaInfo.get("packageName"),
metaInfo.get("className"));
targetedShareIntents.add(targetedShareIntent);
}
String shareVia = "Open with";
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[]{}));
activity.startActivity(chooserIntent);
}
} else {
activity.startActivity(chooser);
}
}
Related
I'm struggling with Android Auto using Desktop Head Unit Emulator.
So far I create my media app in Android Auto, start it and all media albums where shown.
However, when I click on an entry, nothing happens.
It is not clear to me how to catch the click in order to further start an action.
#Nullable
#Override
public BrowserRoot onGetRoot(#NonNull String clientPackageName, int clientUid, #Nullable Bundle rootHints) {
int maximumRootChildLimit = rootHints.getInt(
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
/* defaultValue= */ 4);
Bundle extras = new Bundle();
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM);
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM);
extras.putBoolean(
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true);
return new BrowserRoot(MY_MEDIA_ROOT_ID, extras);
}
#Override
public void onLoadChildren(#NonNull String parentId, #NonNull Result<List<MediaBrowserCompat.MediaItem>> result) {
if (MY_MEDIA_ROOT_ID.equals(parentId)) {
for (Album album : allAlbums) {
.....
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder()
.setMediaId(mediaID)
.setTitle(title)
.setSubtitle(artist)
.setIconBitmap(bitmap(mediaID))
.setExtras(extras)
.build();
mediaItems.add(albumList);
}
}
result.sendResult(mediaItems);
}
I set the mediaID in my PlaybackStateCompat.Builder:
Bundle playbackStateExtras = new Bundle();
playbackStateExtras.putString(
MediaConstants.PLAYBACK_STATE_EXTRAS_KEY_MEDIA_ID,"mediaID");
playbackstateBuilder.setExtras(playbackStateExtras);
I set the mediaID in my MediaMetadataCompat.Builder
metadataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID,"mediaID");
mMediaSessionCompat.setMetadata(metadataBuilder.build());
What have I overlooked in the documentation or didn't understand.
Thanks
Alejandro
note the following procedure, please:
FLAG_Browsable: use this flag, then the MediaID is returned in getLoadChildren.
FLAG_PLAYABLE: use this flag, then the MediaID is returned to onPlayFromMediaID. In onPlayFromMediaIDyou have to put the logic how your mediaPlayerknows what to do with the handed over mediaID
I noticed that you didn't define any tabs. In onLoadChildren several queries on the mediaID make sense. The first level defines the tabs - these are missing in the post or in your code.
e.g:
MediaDescriptionCompat desc =
new MediaDescriptionCompat.Builder()
.setMediaId(AUDIOBOOK_HEADER)
.setTitle("Library")
.build();
MediaBrowserCompat.MediaItem albumList =
new MediaBrowserCompat.MediaItem(desc,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
mediaItems.add(albumList);
desc =
new MediaDescriptionCompat.Builder()
.setMediaId(LATEST_HEADER)
.setTitle("Recently played")
.build();
itemList =
new MediaBrowserCompat.MediaItem(desc,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
mediaItems.add(itemList);
So here's the situation - I'm developing an Unreal plugin with native android features.
Intention for single image works perfectly, but now, when I'm trying to add multiple image attachments using ACTION_SEND_MULTIPLE it's not starting activity.
No errors, execution stops on .startActivity(), wrapping with try-catch doesn't return any exceptions, Unreal passes images array without any issue.
I feel like it's not building intent properly, but after 2 days of searching and countless hours of trials and errors I feel like it's time to give up and seek for advise here :)
Here's the java part of the code I'm suspecting isn't working:
public static void sendEMail(Activity activity, String subject, String[] extraImagePaths,
String[] recipients, String[] cc, String[] bcc,
boolean withChooser, String chooserTitle) {
Intent intent = createEMailIntent(subject, recipients, cc, bcc);
ArrayList<Uri> paths = new ArrayList<Uri>();
if (extraImagePaths.length > 0) {
for (String extraImagePath : extraImagePaths) {
File fileIn = new File(extraImagePath);
Uri arrayPath = Uri.fromFile(fileIn);
paths.add(arrayPath);
}
}
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, paths);
try {
launchShareIntent(activity, withChooser, chooserTitle, intent);
} catch (Exception e) {
Log.d("AndroidLOG:", e.getMessage());
}
}
private static Intent createEMailIntent(String subject, String[] recipients, String[] cc, String[] bcc) {
return new Intent(Intent.ACTION_SEND_MULTIPLE)
.setData(Uri.parse("mailto:"))
.setType("image/*")
.putExtra(Intent.EXTRA_SUBJECT, subject)
.putExtra(Intent.EXTRA_EMAIL, recipients)
.putExtra(Intent.EXTRA_CC, cc)
.putExtra(Intent.EXTRA_BCC, bcc);
}
private static void launchShareIntent(Activity activity, boolean withChooser, String chooserTitle, Intent intent) {
if (withChooser) {
Intent chooserIntent = Intent.createChooser(intent, chooserTitle);
activity.startActivity(chooserIntent);
} else {
activity.startActivity(intent);
}
}
tried removing all of the extras, except images, but that didn't solve the problem.
Help would be much appreciated!
After more digging through SO found similar post, changed .fromFile() to FileProvider and it worked like a charm.
Snippet:
for (String extraImagePath : extraImagePaths) {
Uri arrayPath = FileProvider.getUriForFile(activity, getAuthority(activity), new File(extraImagePath));
paths.add(arrayPath);
}
P.S. Credit goes to CommonsWare!
How to find out the ComponentName of the default system speech recognizer, i.e. the one that is returned when createSpeechRecognizer(Context context) is called? (Actually, I only need to find out which input languages it supports, so if there is an answer only to that, then I'd appreciate it as well.)
The framework solves this by
String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.VOICE_RECOGNITION_SERVICE);
(See the source code of SpeechRecognizer.)
However, this solution does not seem to be available to a third party app.
However, this solution does not seem to be available to a third party app.
I assume you came to such conclusion because Settings.Secure.VOICE_RECOGNITION_SERVICE is not a public API. However, Settings.Secure.getString() requires name of the row to lookup in secure table for the second argument. So, you can simply provide the actual the name of the row you are looking for: "voice_recognition_service".
That's, you can use the same code from SpeechRecognizer with slight change:
String serviceComponent = Settings.Secure.getString(mContext.getContentResolver(),
"voice_recognition_service");
Hope this helps.
UPDATE (I misread the original question)
SpeechRecognizer isn't the thing doing the speech processing, the Intent you pass to SpeechRecognizer, however, is (via startListening(Intent intent)). That intent uses RecognizerIntent.ACTION_RECOGNIZE_SPEECH and, AFAIK, can be detected in the old-fashioned way.
To detect defaults, try resolving the Intent that you want the find the default for but with the PackageManager.MATCH_DEFAULT_ONLY set.
Untested code:
String detectDefaultSpeechRecognizer(Context context) {
final Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
// 1: Try to find the default speech intent
final ResolveInfo defaultResolution = context.getPackageManager().resolveService(speechIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (defaultResolution != null) {
final ActivityInfo activity = defaultResolution.activityInfo;
if (!activity.name.equals("com.android.internal.app.ResolverActivity")) {
//ResolverActivity was launched so there is no default speech recognizer
return "";
}
}
// 2: Try to find anything that we can launch speech recognition with. Pick up the first one that can.
final List<ResolveInfo> resolveInfoList = context.getPackageManager().queryIntentServices(speechIntent, PackageManager.MATCH_DEFAULT_ONLY);
if (!resolveInfoList.isEmpty()) {
speechIntent.setClassName(resolveInfoList.get(0).activityInfo.packageName, resolveInfoList.get(0).activityInfo.name);
return resolveInfoList.get(0).activityInfo.packageName;
}
return "";
}
OLD ANSWER
Check out GAST, it has a way to check if a language is supported in a speech recognizer.
https://github.com/gast-lib/gast-lib/blob/master/library/src/root/gast/speech/SpeechRecognizingActivity.java#L70
You could also try to manually check the <recognition-service> metadata tag.
http://developer.android.com/reference/android/speech/RecognitionService.html#SERVICE_META_DATA
If you only want to find out which input languages the default system speech recognizer supports (createSpeechRecognizer (Context context)), there is a more straightforward way to do it.
All you need to do is using a RecognizerIntent.getVoiceDetailsIntent intent that will check the default system speech recognizer languages:
Intent intent = RecognizerIntent.getVoiceDetailsIntent(getApplicationContext());
if (intent != null) {
ctx.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
#Override
public void onReceive(Context context, final Intent intent) {
Log.d(TAG,
"Receiving Supported Speech Recognition Languages broadcast "
+ intent);
final Bundle extra = getResultExtras(false);
if ((getResultCode() == Activity.RESULT_OK)
&& (extra != null)
&& (mHandler != null)
&& ((extra
.containsKey(RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES)) || (extra
.containsKey(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE)))) {
List<String> supportedLanguages = extra
.getStringArrayList(RecognizerIntent.EXTRA_SUPPORTED_LANGUAGES);
String prefLang = extra
.getString(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE);
}
}
},
null, Activity.RESULT_OK, null, null);
}
I would like to show the 'Add contact' activity prepopulated with a last name (also known as "family name" and "surname"). Currently I can only get it to populate the first name. Here's my code:
Intent intentAddContact = new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI);
intentAddContact.putExtra(ContactsContract.Intents.Insert.NAME, "Mickey Mouse");
intentAddContact.putExtra(ContactsContract.Intents.Insert.PHONE,"01234567891");
intentAddContact.putExtra(ContactsContract.Intents.Insert.EMAIL, "mickey#disney.com");
startActivityForResult(intentAddContact, ADD_CONTACT_REQUEST);
This puts "Mickey Mouse" in the first name field. I need "Mickey" to go in the first name and "Mouse" to go in the last name. My app needs to run on Android 2.1 (API level 7).
Unfortunately, it seems that the stock "Add Contact" activity in AOSP only support the full name to be supplied (see source code for EditContactActivity.createContact() and EntityModifier.parseExtras()).
One way to approximate what you want is to insert the contact information into the provider directly and then launch the "Edit Contact" activity as follow:
private void enlistMickey() throws RemoteException, OperationApplicationException {
final ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder;
builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
builder.withValue(RawContacts.ACCOUNT_NAME, null);
builder.withValue(RawContacts.ACCOUNT_TYPE, null);
ops.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.GIVEN_NAME, "Mickey");
builder.withValue(StructuredName.FAMILY_NAME, "Mouse");
ops.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.NUMBER, "01234567891");
ops.add(builder.build());
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
builder.withValue(Email.DATA, "mickey#disney.com");
ops.add(builder.build());
final ContentResolver cr = getContentResolver();
final ContentProviderResult[] res = cr.applyBatch(ContactsContract.AUTHORITY, ops);
final Uri uri = ContactsContract.RawContacts.getContactLookupUri(cr, res[0].uri);
final Intent intent = new Intent();
intent.setAction(Intent.ACTION_EDIT);
intent.setData(uri);
startActivityForResult(intent, REQUEST_CODE);
}
One notable difference using this "insert and edit" mechanism compared to the "add" method is that the aggregation process in the provider will be more likely to prevent us from creating a new contact if an existing one with matching data already existed.
I'm creating notifications in my Android application, and would like to have an option in my preferences to set what sound is used for the notification. I know that in the Settings application you can choose a default notification sound from a list. Where does that list come from, and is there a way for me to display the same list in my application?
Just copy/pasting some code from one of my apps that does what you are looking for.
This is in an onClick handler of a button labeled "set ringtone" or something similar:
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Select Tone");
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, (Uri) null);
this.startActivityForResult(intent, 5);
And this code captures the choice made by the user:
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
if (resultCode == Activity.RESULT_OK && requestCode == 5) {
Uri uri = intent.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
if (uri != null) {
this.chosenRingtone = uri.toString();
} else {
this.chosenRingtone = null;
}
}
}
Also, I advise my users to install the "Rings Extended" app from the Android Market. Then whenever this dialog is opened on their device, such as from my app or from the phone's settings menu, the user will have the additional choice of picking any of the mp3s stored on their device, not just the built in ringtones.
Or just stick this in your preferences XML:
<RingtonePreference android:showDefault="true"
android:key="Audio" android:title="Alarm Noise"
android:ringtoneType="notification" />
Full content of my sample XML just for context:
<?xml version="1.0" encoding="UTF-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:title="Some value"
android:key="someval"
android:summary="Please provide some value" />
<EditTextPreference android:title="Some other value"
android:key="someval2"
android:summary="Please provide some other value" />
<RingtonePreference android:showDefault="true"
android:key="Audio" android:title="Alarm Noise"
android:ringtoneType="notification" />
</PreferenceScreen>
This is the method I use to get a list of notification sounds available in the phone :)
public Map<String, String> getNotifications() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();
Map<String, String> list = new HashMap<>();
while (cursor.moveToNext()) {
String notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
list.put(notificationTitle, notificationUri);
}
return list;
}
EDIT: This is for the comment regarding how to set the sound in the NotificationCompat.Builder. This method instead gets the ringtone's ID which is what the phone uses, instead of the human readable TITLE the other method got. Combine the uri and the id, and you have the ringtones location.
public ArrayList<String> getNotificationSounds() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
Cursor cursor = manager.getCursor();
ArrayList<String> list = new ArrayList<>();
while (cursor.moveToNext()) {
String id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX);
String uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX);
list.add(uri + "/" + id);
}
return list;
}
The above code will return a list of strings like "content://media/internal/audio/media/27".. you can then pass one of these strings as a Uri into the .setSound() like:
.setSound(Uri.parse("content://media/internal/audio/media/27"))
Hope that was clear enough :)
public void listRingtones() {
RingtoneManager manager = new RingtoneManager(this);
manager.setType(RingtoneManager.TYPE_NOTIFICATION);
// manager.setType(RingtoneManager.TYPE_RINGTONE);//For Get System Ringtone
Cursor cursor = manager.getCursor();
while (cursor.moveToNext()) {
String title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX);
String uri = manager.getRingtoneUri(cursor.getPosition());
String ringtoneName= cursor.getString(cursor.getColumnIndex("title"));
Log.e("All Data", "getNotifications: "+ title+"-=---"+uri+"------"+ringtoneName);
// Do something with the title and the URI of ringtone
}
}
Here's another approach (in Kotlin), build from other answers in this question, that allows you to specify the name of the tone, and then play it:
fun playErrorTone(activity: Activity, context: Context, notificationName: String = "Betelgeuse") {
val notifications = getNotificationSounds(activity)
try {
val tone = notifications.getValue(notificationName)
val errorTone = RingtoneManager.getRingtone(context, Uri.parse(tone))
errorTone.play()
} catch (e: NoSuchElementException) {
try {
// If sound not found, default to first one in list
val errorTone = RingtoneManager.getRingtone(context, Uri.parse(notifications.values.first()))
errorTone.play()
} catch (e: NoSuchElementException) {
Timber.d("NO NOTIFICATION SOUNDS FOUND")
}
}
}
private fun getNotificationSounds(activity: Activity): HashMap<String, String> {
val manager = RingtoneManager(activity)
manager.setType(RingtoneManager.TYPE_NOTIFICATION)
val cursor = manager.cursor
val list = HashMap<String, String>()
while (cursor.moveToNext()) {
val id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
val uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
val title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
list.set(title, "$uri/$id")
}
return list
}
It can probably take some refactoring and optimization, but you should get the idea.