Android Music File Chooser Intent Issue - android

I have an app that lets user choose music file from sdcard. To launch chooser intent I am using
Intent intent = new Intent();
intent.setType("audio/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(
Intent.createChooser(intent, "Complete action using"), 0);
But I am getting different absolute paths depending on what method user choose. If user uses ES File Explorer then I get /sdcard/Music/song.mp3 but if user uses some music app then I am getting /storage/sdcard0/Music/song.mp3. Its very confusing and my app requires me to know one final base path.
Environment.getExternalStorageDirectory() returns /storage/sdcard0/. Any help would be appreciated.
Note: in both cases
Uri uri = Uri.parse(new File(soundPath).getAbsolutePath());
mPlayer = MediaPlayer.create(this, uri);
works fine.

/sdcard is usually symlinked to the real(*) path in the filesystem (the /storage one) to stay compatible with early Android devices where /sdcard was the default.
Using Environment.getExternalStorageDirectory() is the method you should use. There is no guarantee that /sdcard or /storage/sdcard0 will work if you hardcode that path. Device manufacturers can use pretty much any filesystem layout they want but they will make sure that Environment knows the correct path.
(*) Starting Honeycomb & the "unified storage model" the real path is actually something like /data/media which is loop-mounted via fuse to /storage/sdcard0 (or whatever Environment tells you) to enforce the correct permission required for the WRITE_EXTERNAL_STORAGE permission.
Impromptu Q&A Session With Android Engineer Dan Morrill Brings To Light Reasons Behind Galaxy Nexus' Lack Of USB Mass Storage - second question has some details.
getAbsolutePath() is by the way not working in the same way as it does for desktop Java apps. On Android, the current working directory / root is always /. So getAbsolutePath() would always return the same as getPath() does and will at most prefix the path with a /.
Uris from File can be easily constructed via
Uri uri = Uri.fromFile(new File(soundPath));
that way you get a correct Uri using the file:// scheme which is not the case (and could lead to errors) if you use Uri.parse("/some/path")

Getting full path and file name:
Private String getPath(Uri u) {
String[] projection = { MediaStore.Audio.Media.DATA };
Cursor cursor = managedQuery(u, projection, null, null, null);
int column_index = cursor
.getColumnIndexOrThrow(MediaStore.Audio.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
use this on onActivityResult
String mpath = getPath(data.getData());

Related

is the Uri returned by Intent.ACTION_GET_CONTENT always somewhere on disk, or can it be anywhere?

Currently I have the following code that allows a user to choose an image.
int requestCode = 1337;
Intent chooserIntent = new Intent(Intent.ACTION_GET_CONTENT);
chooserIntent.setType("image/*");
chooserIntent = Intent.createChooser(chooserIntent, "Please choose a picture");
chooserIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivityForResult(chooserIntent, requestCode);
My question is:
does Android guarantee that the returned Uri is always pointing to a location on disk, or is it possible that it might be pointing to somewhere on the internet too?
P.S. although I am not sure about this, the Uri returned by this piece of code seems to always start with content:// - I am not sure whether or not this holds for all possible return values, I thought I would just add this here to help out any possible question answerers.
does Android guarantee that the returned Uri is always pointing to a
location on disk, or is it possible that it might be pointing to
somewhere on the internet too?
It is possible to have Uri other than local disk i.e. it can be remotely as well. You will get URL from remote then convert it to Uri and use it.
From official docs:
An ACTION_GET_CONTENT could allow the user to create the data as it
runs (for example taking a picture or recording a sound), let them
browse over the web and download the desired data, etc.
Convert Url to a Uri (Reference):
Uri uri = Uri.parse( "http://www.facebook.com" );

android implicit intent edit failed to load image

This is my implicit intent to invoke image editing apps on the device:
startActivity(new Intent(Intent.ACTION_EDIT).setDataAndType(myUri,
getMimeType(myUri)).setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION));
And this is how I getMimeType:
public String getMimeType(Uri uri) {
String mimeType = null;
if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
ContentResolver cr = getContentResolver();
mimeType = cr.getType(uri);
} else {
String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
.toString());
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
fileExtension.toLowerCase());
}
return mimeType;
}
For some apps it crashes to load:
On the app Sketch_Camera only an invisible page loads up and disables interaction with my app.
On the app AirBrush it loads the app but crashes with this message "Failed to load image".
Is it related to minimum sdk version as mine is 16?
I've tested this on minimum sdk version of 9 too and no change in result.
Is there anything else that I should add to this intent to work with all the apps?
I've tried putExtra too and it doesn't help:
.putExtra(Intent.ACTION_EDIT, myUri);
I've some gallery apps on my device and all of them launch Sketch_Camera and AirBrush without any problem.
What's happening here? I'm so confused after two days of struggling with this phenomena.
It's a file created from path of one media store file by querying MediaStore.Images.Media.EXTERNAL_CONTENT_URI
There is no guarantee that the other app has rights to this location, or even that your app has rights to this location. For example, the image could be on removable storage. Besides, the file Uri scheme is being banned for cross-app usage, anyway.
Use a content Uri instead. For example, in this sample app, I query MediaStore for videos. Given a Cursor named row positioned at a particular video, I generate the Uri for it via:
videoUri=
ContentUris.withAppendedId(
MediaStore.Video.Media.EXTERNAL_CONTENT_URI, row.getInt(row.getColumnIndex(MediaStore.Video.Media._ID)));
This Uri both works for my own purposes (e.g., hand to Picasso to get a thumbnail, hand to VideoView for playback) and for handing to third-party apps (e.g., ACTION_VIEW for playback).
Other than changing the base Uri to the one you queried against (MediaStore.Images.Media.EXTERNAL_CONTENT_URI), the same basic code should work for you.
Also, get rid of the flags from your Intent. Those are only for where the Intent points to your own ContentProvider, which is not the case in either your original code or with the Uri that you create from withAppendedId().

Image path from Android camera intent

I am trying to figure out the right way to get a file path from the camera after a picture is taken:
Launch the camera intent. Since I am telling the camera to write to internal storage give it the uri and read permission:
File file = new File(context.getFilesDir(), "picture.jpg");
Uri uri = FileProvider.getUriForFile(getApplicationContext(), "my.app.package.fileprovider", file);
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(intent, ACTION_IMAGE_CAPTURE_REQUEST_CODE);
Listen for the camera intent result:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case ACTION_IMAGE_CAPTURE_REQUEST_CODE:
// How do I get the local path to the file here?
break;
}
}
What is the best way to get the file path on camera intent return. Sure I can save off the file path to a member variable before launching the intent, but that seems bad, seems I should get the path from the onActivityResult.
I have tried this (Get Image path from camera intent):
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = managedQuery(MediaStore.Images.Media.INTERNAL_CONTENT_URI,projection, null, null, null);
int column_index_data = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToLast();
String imagePath = cursor.getString(column_index_data);
Only difference being that I am using INTERNAL_CONTENT_URI as I am trying to store the pic internally. If I do this I get an exception:
Error getting data column
java.lang.IllegalArgumentException: column '_data' does not exist
Am I going about this wrong. I want to take a pic and store that pic in internal storage.
EDIT:
One more thought. Should I be storing pics internally? I am right not because of the new android M permissions. I already have to ask the user for Camera permission, if I store pic to external storage, I have to ask the user for permission to write to to external. Lastly if I store the image externally, it is readable by all applications on the device that can read from external storage. This may be a privacy issue for my app.
What is the best way to get the file path on camera intent return
You specified the file path in EXTRA_OUTPUT. Either the camera put the photo there, or it didn't. If it did, then you already know the file path: new File(context.getFilesDir(), "picture.jpg"). That should be the case the vast majority of the time.
Some camera apps are buggy and will ignore EXTRA_OUTPUT. In those cases:
You can see if the camera app returned a Uri in the Intent passed to onActivityResult(), then use ContentResolver and openInputStream() to begin the process of copying the image to your desired location, or
You can see if the "data" extra exists, in which case that's a Bitmap thumbnail that you can save to your desired location.
In all of these cases, the file path is what you specified; it is merely a question of whether or not it takes additional work for you to get the image there.
Sure I can save off the file path to a member variable before launching the intent, but that seems bad
I have no idea why you would think that.
seems I should get the path from the onActivityResult
You are welcome to believe whatever you want. Camera app developers are hopefully reading the documentation for ACTION_IMAGE_CAPTURE. That documentation does not state that the camera app has to return anything if you provide EXTRA_OUTPUT. Hence, many camera apps will not return anything.
UPDATE based on edit:
Should I be storing pics internally?
Um, you already are.
if I store pic to external storage, I have to ask the user for permission to write to to external
That depends on where you are writing. getExternalFilesDir(), getExternalCacheDir(), and kin from Context do not require a permission on Android 4.4+. The methods you call on Environment for external storage locations (e.g., getExternalStoragePublicDirectory()) do require WRITE_EXTERNAL_STORAGE, which is a dangerous permission and would need to be requested at runtime.

How to get any type of file with intent.createChooser android

I have this code:
protected void pickFile(View view){
///Codigo que abre la galeria de imagenes y carga la imagen en displayedImage
Intent intent = new Intent();
intent.setType("file/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Choose File to Upload"), 1);
}
//It's executed when leaving file system
#Override
protected void onActivityResult(int reqCode, int resCode, Intent data){
super.onActivityResult(reqCode, resCode, data);
if (reqCode == 1 && resCode == RESULT_OK && data != null) {
Uri selectedFile = data.getData();
RequestMaker.uploadFile(this, selectedFile, "this is a file");
}
}
What i want to do is to be able to select any file from my phone and send it.
The code works, it opens the chooser and lets me search for any file. However, there are a few problems i am having:
When i try to access via "Internal Storage" option, i cannot select any item. They are all disabled. I fixed that installing a file manager and it lets me choose the files i want, but maybe there is a quick fix for that.
When i select the file and run Uri.getPath(), sometimes the path is valid, others, and in general when i am selecting some image file, there is an error with the path i get in return. Is not the actual one.
I saw some fixes online but they are all for selecting images from the galery, i want the general one.
How can i fix this?
The code works
No, it does not.
First, file/* is not a valid MIME type, or even a wildcard MIME type. There is no MIME type that begins with file/. If you want any MIME type, try */*.
Second, ACTION_GET_CONTENT does not allow the user to "select any file". It allow the user to pick a piece of content, from any app on the device that implements an ACTION_GET_CONTENT activity that elects to honor your MIME type. What is returned by that activity is a Uri pointing to the content. This does not have to be a local file, let alone one that you have direct filesystem access to.
When i select the file and run Uri.getPath(), sometimes the path is valid
No, the path is always valid (at least, for a while). It just is not what you think it is. A Uri is not a file.
For example, presumably you are viewing this Web page in a Web browser. If you look in the address bar of that Web browser, you will see the following URL:
https://stackoverflow.com/questions/33575449/how-to-get-any-type-of-file-with-intent-createchooser-android
By your way of thinking, this is referring to a file, on your hard drive, located at /questions/33575449/how-to-get-any-type-of-file-with-intent-createchooser-android.
That is not the case. Part of the URL indicates a location where the path is relevant; in this case, it refers to a Web server.
A Uri is the same thing. In particular, if the Uri has a scheme other than file:, the Uri is simply an address, one that does not necessarily map to anything you can get to directly. Just as Web browser developers use HTTP to get a stream on the contents of this Web page, so you must use ContentResolver and openInputStream() to get at the contents of content: Uri values.
How can i fix this?
Either:
Use the Uri as a Uri, with openInputStream(), getType(), and similar methods on ContentResolver, or
Do not use ACTION_GET_CONTENT, but instead build your own UI for browsing files that your app happens to be able to reach. This will be a subset of all the files on the device, as not everything is in a location that your app has access to (e.g., files on removable media will be missed). But, it synchronizes your code with your mental model (i.e., that you want files, not content).

Gmail 5.0 app fails with "Permission denied for the attachment" when it receives ACTION_SEND intent

My app creates mails with attachments, and uses an intent with Intent.ACTION_SEND to launch a mail app.
It works with all the mail apps I tested with, except for the new Gmail 5.0 (it works with Gmail 4.9), where the mail opens without attachment, showing the error: "Permission denied for the attachment".
There are no useful messages from Gmail on logcat. I only tested Gmail 5.0 on Android KitKat, but on multiple devices.
I create the file for the attachment like this:
String fileName = "file-name_something_like_this";
FileOutputStream output = context.openFileOutput(
fileName, Context.MODE_WORLD_READABLE);
// Write data to output...
output.close();
File fileToSend = new File(context.getFilesDir(), fileName);
I'm aware of the security concerns with MODE_WORLD_READABLE.
I send the intent like this:
public static void compose(
Context context,
String address,
String subject,
String body,
File attachment) {
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("message/rfc822");
emailIntent.putExtra(
Intent.EXTRA_EMAIL, new String[] { address });
emailIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
emailIntent.putExtra(Intent.EXTRA_TEXT, body);
emailIntent.putExtra(
Intent.EXTRA_STREAM,
Uri.fromFile(attachment));
Intent chooser = Intent.createChooser(
emailIntent,
context.getString(R.string.send_mail_chooser));
context.startActivity(chooser);
}
Is there anything I do wrong when creating the file or sending the intent? Is there a better way to start a mail app with attachment? Alternatively - has someone encountered this problem and found a workaround for it?
Thanks!
I was able to pass a screenshot .jpeg file from my app to GMail 5.0 through an Intent. The key was in this answer.
Everything I have from #natasky 's code is nearly identical but instead, I have the file's directory as
context.getExternalCacheDir();
Which "represents the external storage directory where you should save cache files" (documentation)
GMail 5.0 added some security checks to attachments it receives from an Intent. These are unrelated to unix permissions, so the fact that the file is readable doesn't matter.
When the attachment Uri is a file://, it'll only accept files from external storage, the private directory of gmail itself, or world-readable files from the private data directory of the calling app.
The problem with this security check is that it relies on gmail being able to find the caller app, which is only reliable when the caller has asked for result. In your code above, you do not ask for result and therefore gmail does not know who the caller is, and rejects your file.
Since it worked for you in 4.9 but not in 5.0, you know it's not a unix permission problem, so the reason must be the new checks.
TL;DR answer:
replace startActivity with startActivityForResult.
Or better yet, use a content provider.
Use getExternalCacheDir() with File.createTempFile.
Use the following to create a temporary file in the external cache directory:
File tempFile = File.createTempFile("fileName", ".txt", context.getExternalCacheDir());
Then copy your original file's content to tempFile,
FileWriter fw = new FileWriter(tempFile);
FileReader fr = new FileReader(Data.ERR_BAK_FILE);
int c = fr.read();
while (c != -1) {
fw.write(c);
c = fr.read();
}
fr.close();
fw.flush();
fw.close();
now put your file to intent,
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
You should implement a FileProvider, which can create Uris for your app's internal files. Other apps are granted permission to read these Uris. Then, simply instead of calling Uri.fromFile(attachment), you instantiate your FileProvider and use:
fileProvider.getUriForFile(attachment);
Google have an answer for that issue:
Store the data in your own ContentProvider, making sure that other apps have the correct permission to access your provider. The preferred mechanism for providing access is to use per-URI permissions which are temporary and only grant access to the receiving application. An easy way to create a ContentProvider like this is to use the FileProvider helper class.
Use the system MediaStore. The MediaStore is primarily aimed at video, audio and image MIME types, however beginning with Android 3.0 (API level 11) it can also store non-media types (see MediaStore.Files for more info). Files can be inserted into the MediaStore using scanFile() after which a content:// style Uri suitable for sharing is passed to the provided onScanCompleted() callback. Note that once added to the system MediaStore the content is accessible to any app on the device.
Also you can try set permissions for your file:
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
And finally you can copy/store your files in external storage - permissions not needed there.
I tested it and I found out that it was definitely private storage access problem.
When you attach some file to Gmail (over 5.0) do not use the file from private storage such as /data/data/package/. Try to use /storage/sdcard.
You can successfully attach your file.
Not sure why GMail 5.0 doesn't like certain file paths (which I've confirmed it does have read access to), but an apparently better solution is to implement your own ContentProvider class to serve the file. It's actually somewhat simple, and I found a decent example here: http://stephendnicholas.com/archives/974
Be sure to add the tag to your app manifest, and include a "android:grantUriPermissions="true"" within that. You'll also want to implement getType() and return the appropriate MIME type for the file URI, otherwise some apps wont work with this... There's an example of that in the comment section on the link.
I was having this problem and finally found an easy way to send email with attachment. Here is the code
public void SendEmail(){
try {
//saving image
String randomNameOfPic = Calendar.DAY_OF_YEAR+DateFormat.getTimeInstance().toString();
File file = new File(ActivityRecharge.this.getCacheDir(), "slip"+ randomNameOfPic+ ".jpg");
FileOutputStream fOut = new FileOutputStream(file);
myPic.compress(Bitmap.CompressFormat.JPEG, 100, fOut);
fOut.flush();
fOut.close();
file.setReadable(true, false);
//sending email
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"zohabali5#gmail.com"});
intent.putExtra(Intent.EXTRA_SUBJECT, "Recharge Account");
intent.putExtra(Intent.EXTRA_TEXT, "body text");
//Uri uri = Uri.parse("file://" + fileAbsolutePath);
intent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file));
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivityForResult(Intent.createChooser(intent, "Send email..."),12);
}catch (Exception e){
Toast.makeText(ActivityRecharge.this,"Unable to open Email intent",Toast.LENGTH_LONG).show();
}
}
In this code "myPic" is bitmap which was returned by camera intent
Step 1: Add authority in your attached URI
Uri uri = FileProvider.getUriForFile(context, ""com.yourpackage", file);
Same as your manifest file provide name
android:authorities="com.yourpackage"
Step 2`; Add flag for allow to read
myIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

Categories

Resources