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().
Related
I am trying to implement gallery functionality in kotlin with android studio using default component. The goal is to click a button and open the desired path as a common Intent .
I can't understand how it is possible that the emulator only opens recent images for me instead of the path I specified on the file provider.
enter image description here
The part of code that I'm using is :
fun dispatcherGalleryImage(){
try {
val uri: Uri = Uri.parse(Environment.getExternalStoragePublicDirectory(requireActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES).toString().replace("/storage/emulated/0", "")).path.toString())
Log.info(uri.toString())
val intent=Intent(Intent.ACTION_GET_CONTENT)
intent.type ="image/*"
startActivityForResult(intent, TAKE_GALLERY_IMAGE)
}catch (ex: Exception){
Log.info(ex.toString())
}
}
I can't understand how it is possible that the emulator only opens recent images for me instead of the path I specified on the file provider
First, you are not using uri, except to log its value.
Second, the string that you are passing to Uri.parse() is not a valid string representation of a Uri (use Uri.fromFile() for a File).
Third, you are not using FileProvider. And a file:/// Uri is largely useless on Android 7.0+, as it will trigger a FileUriExposedException if you pass it in an Intent.
Fourth, ACTION_GET_CONTENT does not take a Uri as input. It is unclear why you are using ACTION_GET_CONTENT when you already have the content.
If your objective is to let the user view the image in their desired image viewer, use ACTION_VIEW, and put the FileProvider-supplied Uri and concrete (non-wildcard) MIME type in the Intent.
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" );
As android is increasing its security and changing some stuff now a days to make Android OS more secure to Vulnerabilities, its a good thing to understand new structure for developers to get into it.
I have been away from development from last couple of years.I just noticed that some stuff have been changed e.g file scheme of URI.
Case 1 : :
I am launching camera intent with custom URI mentioned below.
var takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Ensure that there's a camera activity to handle the intent
if (takePictureIntent.resolveActivity(packageManager) != null) {
// Create the File where the photo should go
var photoFile: File? = null
try {
photoFile = createImageFile()
} catch (ex: IOException) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (photoFile != null) {
val photoURI: Uri = FileProvider.getUriForFile(this,
BuildConfig.APPLICATION_ID + ".fileprovider",
photoFile)
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO)
}
}
photoURI is custom URI.
When i took picture and check URI it has file:// in start from where i can get file path and i can do some stuff with it(i am uploading it to server)
Case 2
I am launching simple intent to pick image from gallery
val intent = Intent()
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)
intent.action = Intent.ACTION_GET_CONTENT
startActivityForResult(Intent.createChooser(intent, "Select Picture"), TYPE_IMAGE)
and now when i pick image and check URI its has content:// in start. And the trouble starts when i try to get file path from it. i have been searching into SOF but none of solutions worked for me.
My main purpose it get file path is that i want to pick file from device and upload it to server.But with content:// scheme i can not make a file from URI and thus can not send this file server.
i just read CommonsWareBlog about scheme system. it cleared few things but i still don't know how can i switch from content:// too file:// in my onActivityResult!
i would like to mention it again that current answers on SOF are not working for me that's why i have to ask this question in details.
I am using 7.0 device emulator
This is just an example of my problem in actual i am picking up PDF and DOCX files and want to upload them on server and after picking files i have URI with content:// scheme and i can not make a file and send to server.
i am not sure what i am missing about schemes.
I hope this question will help many other developers which are new to file system.
pleasure try to explain by code example according to my problem of picking file from intent (if you like)
Since Android 7.0, returning file:// scheme is discouraged (android changelog), since it can leak files from private folders. You can still encounter it, especially when using apps that target below Nougat.
Short answer is when you query for Intent.ACTION_GET_CONTENT You receive content:// uri that you can ContentResolver.openInputStream(Uri) on to obtain the data. This ensures item you picked is read-only, and masks it's real source - it might've been a file, provided remotely or generated by provider all together.
I use the Intent mechanism to have the user select an image via the standard way
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "image/*"
intent.addCategory(Intent.CATEGORY_OPENABLE)
ctx.startActivityForResult(intent, RequestCodes.SelectPhoto)
then I pass the Uri to another activity to maybe crop the photo. I need the Uri before to do some pre-checks.
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity. However, there is a "weird" provider in Asia, com.miui.gallery.provider.GalleryOpenProvider that doesn't -- an evil SecurityException happens in the cropper.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot, but unfortunately that one doesn't support Google Photos in the cloud, in the emulator.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT, and if so either prevent it, or otherwise fall back to using ACTION_OPEN_DOCUMENT. I'd like to avoid copying the stream before giving the Uri to the cropper, the crop activity treats it as readonly anyway.
This the full function to start the crop (kotlin). CropActivity is a modification of the old open-source Gallery app com.android.gallery3d.
private fun startCrop(ctx: Activity, uri: Uri) {
val intent = Intent(ctx, CropActivity::class.java)
intent.data = uri
val file = this.createImageFile(ctx, "photofinal")
if (file == null) {
this.showStorageUnavailable(ctx)
return
}
val outputUri = Uri.fromFile(file)
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
intent.putExtra(CropExtras.KEY_MIN_CROP_SIDE, Config.minimumImageDimension)
intent.putExtra(CropExtras.KEY_MOST_OBLONG_ASPECT, Config.maxPhotoAspectRatio)
intent.putExtra(CropExtras.KEY_EXIF_ORIENTATION, exifOrientation)
ctx.startActivityForResult(intent, RequestCodes.CropPhoto)
}
then I pass the Uri to another activity to maybe crop the photo
Pass that Uri in the "data" facet of the Intent, and add FLAG_GRANT_READ_URI_PERMISSION to transfer read access to the other component. See this sample app:
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
if (resultCode==Activity.RESULT_OK) {
getActivity()
.startService(new Intent(getActivity(), DurablizerService.class)
.setData(resultData.getData())
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
}
}
Here, I happen to be passing the Uri to a service, but the same principle holds for an activity.
See also this blog post for more about Uri access lifetimes.
Or, don't use separate activities, but do something else (e.g., multiple fragments).
On the Android emulators, the default providers such as Photos (apparently) give my whole app permission to open the Uri, not just the requesting activity.
That would occur if the Uri has a file scheme or is from an exported permission-less ContentProvider.
So I try to use ACTION_OPEN_DOCUMENT, which per the specs say that it will give my whole app permission until device reboot
It is subject to the same general rules as the Uri values you get from ACTION_GET_CONTENT.
So I am looking for a way to determine if com.miui.gallery.provider.GalleryOpenProvider is going to be on the list for GET_CONTENT
That's not strictly possible. Any app could return a Uri from that provider. In practice, that provider may only be used by its hosting app. If you found the package name for that provider's app, and you used queryIntentActivities() on PackageManager with your ACTION_GET_CONTENT Intent, you could determine if an activity from that app is in the list of ACTION_GET_CONTENT implementations.
However, if you use FLAG_GRANT_READ_URI_PERMISSION, as I note earlier, that should not be necessary.
if so either prevent it
Other than by rolling your own "chooser"-style UI, that's not strictly possible.
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());