I've included in my app the possibility to insert content with the "share with" button from the phone gallery, but in different devices i have different results. For example, if i retrieve the uri of the content from the Intent with the code
val fileUri: Uri = intent.getParcelableExtra(Intent.EXTRA_STREAM).asInstanceOf[Uri]
in galaxy S3 I have: content://media/external/images/media/812
and in nexus 7 I have: file:///storage/sdcard0/DCIM/Camera/ContactPhoto-IMG_20131119_173230.jpg
if i use the function:
val projection: Array[String] = Array(MediaColumns.DATA)
val cursor :Cursor= act.getContentResolver.query(uri,projection,null,null,null)
val column_index = cursor.getColumnIndexOrThrow(MediaColumns.DATA)
cursor.moveToFirst()
cursor.getString(column_index)
in this two different type of Uri, the second one give me error because the cursor is null
The problem is that sometimes the action_send is coming with a file:\ URI, and sometimes with a content:\ URI. And I need to spot file:\ and convert it to a content one.
What can I do to have the same Uri structure in all the devices?
Other apps are welcome to send whatever Uri values that they want. If your <intent-filter> says that you support those schemes, you will get those Uri values and have to deal with them.
Note that you should not be using your code snippet anyway. There is no requirement that any Uri that is given to you be in the MediaStore.
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.
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.
When I share a picture from gallery, in my activity that receives the intent I get the content URI but uri.getAuthority() returns "0#media". The trouble is that even for a PDF, it returns the same value for getAuthority(). How do I distinguish between a media file and another file (eg: PDF) in my activity ?
Am facing this issue only in MM 6.0.1, appreciate your help.
ContentResolver cR = context.getContentResolver();
MimeTypeMap mime = MimeTypeMap.getSingleton();
String type = mime.getExtensionFromMimeType(cR.getType(pass_your_URI));
So, if the selected file is an image of jpeg extension, the type returns jpeg. However, just invoking cR.getType(pass_your_URI) will return mime/jpeg which you can also use to differentiate media type out of URI.
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().
I am using an intent to invoke a file chooser that is external to my app. Some of the file chooser applications return an Uri of scheme "content".
I need to obtain the last modified date of the chosen object. How do I do that when the scheme is "content"? I didn't find an appropriate API.
There is some API that returns a FileDescriptor. But I don't get the last modified date from a FileDescriptor. Any help appreciate.
Best Regards
In general you can't do what you want - there is no API to get a File for an item described by a "content" URI because a content URI does not have to correspond with a file anyway.
In practice it is possible to get a File path for some content URIs:
as you describe, sometimes you can get lucky, and manipulate the content uri by changing the content scheme to a file scheme
If the content URI came from the Media store, then you can do a query to get the file path
public static String getPathnameFromMediaUri(Activity activity, Uri contentUri)
{
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = activity.managedQuery(contentUri, projection, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
There are a whole host of other questions asking pretty much the same thing that provide further ideas (or slightly different working of the same ideas)
Android file chooser
How to extract the file name from URI returned from Intent.ACTION_GET_CONTENT?
URI from Intent.ACTION_GET_CONTENT into File