android - share image via intent - uri permissions - android

Referring to my previous question.
I want to ask something specific to per-URI permissions. I understand that to send binary data like an image whose URI is held by a provider in your app, two things must be done :
1) Provider should set android:grantUriPermission
2) Uri permissions must be granted either via Intent.setFlags or context.grantUriPermission
I wanted to see the effect when I don't do step 2. My provider is :
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.android.provider.DataSharing"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
App1 sharing the image with App2 does the following :
File imagePath = new File(getApplicationContext().getFilesDir(), "images");
File newFile = new File(imagePath, "earth.jpg");
Uri contentUri = FileProvider.getUriForFile(getApplicationContext(),
"com.android.provider.DataSharing", newFile);
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.putExtra(Intent.EXTRA_STREAM, contentUri);
i.setType("image/jpeg");
startActivity(i);
My receiving app (App2) does :
Uri imageUri = (Uri) intent.getParcelableExtra(Intent.EXTRA_STREAM);
if (imageUri != null){
ImageView iv = (ImageView) findViewById(R.id.imageView);
iv.setImageURI(imageUri);
}
But somehow even without granting the URI permission through intent or context, I can see the image in App2's image view. I am testing in Android 4.4.
Can someone help me explain this behavior ? What did I do that App2 still has the rights to access the image ?

According to the documentation for FileProvider, Context.grantUriPermission():
The permission remains in effect until you revoke it by calling
revokeUriPermission() or until the device reboots.
However, if you use the Intent flag:
Permissions granted in an Intent remain in effect while the stack of
the receiving Activity is active. When the stack finishes, the
permissions are automatically removed.
So it looks like it's the expected behavior (if you're testing with the same image / same Uri).

Related

createChooser() intent exception "requires the provider be exported, or grantUriPermission()"

The app needs to share a PDF file stored in the root of the cacheDir with other apps. The issue is seen on Android 12, possibly other versions too.
Manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
Provider paths:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<cache-path name="cache" path="." />
</paths>
Intent:
val pdfFile = File(requireContext().cacheDir, pdfFileName)
val fileUri: Uri = FileProvider.getUriForFile(
requireContext().applicationContext,
requireContext().packageName.toString() + ".provider",
pdfFile
)
val intent = Intent()
intent.action = Intent.ACTION_SEND
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
intent.type = "application/pdf"
startActivity(Intent.createChooser(intent, "Share Document"))
The share sheet successfully opens but this exception always shows at that point and subsequently sharing to another app fails.
Writing exception to parcel
java.lang.SecurityException: Permission Denial: reading
androidx.core.content.FileProvider uri
content://uk.co.packagename.provider/cache/8BEDF7212-0DE46-42B0-9FA9-32C434BDD2F3HO.pdf
from pid=15363, uid=1000 requires the provider be exported, or grantUriPermission()
The provider as a whole cannot be exported and the URI permission appears to already be granted. I've read through the Android file sharing docs and many S/O answers but I cannot see what needs correcting, can you?
One of the limitations of FileProvider.getUriForFile() is that it does not check to see if the file exists. There are legit reasons for getting a Uri to a file that does not exist, such as for ACTION_IMAGE_CAPTURE. Still, it means that just getting the Uri is no guarantee that that the Uri is useful for reading content.
Compounding that problem is that "does the file exist" via exists() feels like it may be a bit dicey, especially for external storage.
So, it's pretty important to make sure that you have the right File object, and that it should point to an already-existing file, before you call getUriForFile().
After checking what package caused this exception it turned out to be android system. So after granting uri permission, I see no more exceptions like this in the logs:
requireContext().grantUriPermission("android", fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
The reason a security exception ("requires the provider be exported, or grantUriPermission()") is thrown is because the android system is trying to access the shared resource to give the user a preview of what would be shared.
The best approach is to use the intent.setClipData to share the resource in addition to intent.putExtra(Intent.EXTRA_STREAM, fileUri)
The code should look like this:
intent.action = Intent.ACTION_SEND
intent.setClipData(ClipData.newRawUri("", fileUri))
intent.putExtra(Intent.EXTRA_STREAM, fileUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
#skvalex's answer above works, but it is a hacky way of giving the Android system access to the file for preview in the application share picker screen.
For a additional reference on this, please take a look at the FileProvider class as well.

How do you share images via an intent in an instant app?

We are building a quiz instant app, where the user can complete the quiz and then share their result. We share some text with a link, and also an image that shows the user's quiz result. There is no problem when we go through this flow in the installed app, however in the instant app, the image fails to share.
Here is how we generate the intent:
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", image)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, content)
putExtra(Intent.EXTRA_STREAM, uri)
type = "image/*"
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
activity?.startActivity(Intent.createChooser(shareIntent, getString(R.string.quiz_share_title)))
Here is the provider in our base application manifest:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/fileprovider" />
</provider>
When the user shares the image in the instant app, this error message appears in logcat:
java.lang.SecurityException: Permission Denial: reading androidx.core.content.FileProvider uri content://com.redacted.fileprovider/shared/1563809004297.png from pid=29184, uid=1000 requires the provider be exported, or grantUriPermission()
I have tried setting exported="true", and that crashes the instant app on startup with the following exception:
java.lang.RuntimeException: Unable to get provider androidx.core.content.FileProvider: java.lang.SecurityException: Provider must not be exported
I'm guessing that instant apps can't use the FLAG_GRANT_READ_URI_PERMISSION flag, for the same reason that they can't use the WRITE_EXTERNAL_STORAGE permission.
Is there another way we can share images in instant apps?
An instant app can not have an exported ContentProvider. This is a security restriction and crashing the app here is working as intended.
You could use InstantApps.showInstallPrompt() before firing the Intent in order to get users to install the app before doing this. Please make sure that you display a message containing your rationale or otherwise you might confuse your users.
There are other ways to share images using instant apps. But these depend on where the image is coming from. In case of an external content provider (i.e. the Camera app's) you should be able to forward the URI.
use this piece of code to share image from directory:
private void shareImage() {
Intent share = new Intent(Intent.ACTION_SEND);
// If you want to share a png image only, you can do:
// setType("image/png"); OR for jpeg: setType("image/jpeg");
share.setType("image/*");
// Make sure you put example png image named myImage.png in your
// directory
String imagePath = Environment.getExternalStorageDirectory()
+ "/myImage.png";
File imageFileToShare = new File(imagePath);
Uri uri = Uri.fromFile(imageFileToShare);
share.putExtra(Intent.EXTRA_STREAM, uri);
startActivity(Intent.createChooser(share, "Share Image!"));
}
You can share anythings using Intent class
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
intent.putExtra(Intent.EXTRA_TEXT, "Image URL");
startActivity(intent);

File provider - Files not accessible even in the own app

I am implementing a File Provider, followed the doc carefully but unable to use the files in the app itself or share any file.
Added manifest, created xml file for dir sharing, generating content uri, granting uri permission, tried sharing files with cache, external, internal dir but nothing is working.
Searched the net but found nothing which is missing in the code.
Below is the code:
Manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="package.widget.fileprovider"
android:exported="false"
android:grantUriPermissions="true" >
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/filepaths" />
</provider>
filepaths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="widgetDoc" path="."/>
</paths>
GetUri:
private Uri getFileUri(Context context, String name){
File newFile = new File(context.getCacheDir() + File.separator + StorageUtil.INTERNAL_DIR, name);
Uri contentUri = FileProvider.getUriForFile(context, "package.widget.fileprovider", newFile);
return contentUri;
}
Code to access pdf file:
Intent target = new Intent(Intent.ACTION_VIEW);
Uri uri = getFileUri(getApplicationContext(), file);
target.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
target.setDataAndType(uri,"application/pdf");
Intent intent = Intent.createChooser(target, "Choose Pdf Viewer...");
startActivity(intent);
Code to access image:
imageview.setImageURI(getFileUri(getApplicationContext(), file));
Kindly help me out where I am going wrong, not even able to use these files in my own app too.
Thanks in advance.
I need to show these image files in a widget and now if am accessing the image files from the widget then it is giving IllegalArgumentException: Failed to find configured root however it is working fine with activity
Ideally, use setImageViewBitmap(), and make sure that your image is small (under 1MB in heap space, such as less than 512x512).
While the RemoteViews can accept a Uri, you have good way of associating Intent.FLAG_GRANT_READ_URI_PERMISSION with a RemoteViews for the purposes of populating an ImageView. And, you cannot export your FileProvider, as FileProvider does not allow that.
You can attempt to identify the user's chosen home screen and use grantUriPermission() to grant the home screen access to this Uri, but I would expect that solution to be fragile.

issue opening document using FLAG_GRANT_WRITE_URI_PERMISSION intent ANDROID

I would like to open my documents saved in application storage using third party applications like polaris office,quickoffice,kingsoft docs etc using chooser.I would like to grant permission to edit the document as well.
If i open the document using FLAG_GRANT_READ_URI_PERMISSION,the document opens fine but if i use FLAG_GRANT_WRITE_URI_PERMISSION insted the external application pushedup but donot display the document selected.Incase of polaris office,the document opens fine using FLAG_GRANT_READ_URI_PERMISSION but if i use FLAG_GRANT_WRITE_URI_PERMISSION it says document type not supported.
The code i use is
File file = new File(
getFilePath(data.getUniqueId(), data.getFileName()));
Uri uri = FileProvider.getUriForFile(context, "com.example.com",
file);
Intent intent = new Intent();
intent.setAction(Intent.ACTION_EDIT);
intent.setDataAndType(uri, "application/pdf");
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Intent chooserIntent=Intent.createChooser(intent, uri.toString());
context.startActivity(chooserIntent);
In manifest i have mentioned
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.com"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/my_paths" />
</provider>
Please let me know where i am wrong.
I would like to provide temporary permission to third part apps to open and edit the document selected.The documents need to be stored in application storage space.
instead of the line:
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
just put the line:
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
I think your provider should also have android:exported attribute set to true (in order to be used by other applications).

Sharing cached images using FileProvider

I have an app that uses Universal Image Loader to download photos from the internet, cache them to data/data/com.myapp/cache and display in ImageViews.
I also wanted to add sharing (WhatsApp, Facebook, Instagram, Dropbox etc.) to my app, so I tried to use this code:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, photoUri);
intent.setType("image/jpeg");
startActivity(Intent.createChooser(intent, "Share image"));
photoUri was the uri in cache folder which other apps don't have permission to read.
So I googled/stackoverflowed and found out that I need to use FileProvider.
I configured the FileProvider as follows (as was written here) in my manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.myapp.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/file_paths" />
</provider>
And the file_paths.xml:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="photo_cache" path="/"/>
</paths>
Then I use the following code in my activity in button onClickListener:
File photoFile = ImageLoader.getInstance().getDiscCache().get("HTTP LINK HERE");
// returns File of "/data/data/com.myapp/cache/-1301123243"
Uri photoUri = FileProvider.getUriForFile(MyActivity.this, "com.myapp.fileprovider", photoFile);
// returns Uri of "content://com.myapp.fileprovider/photo_cache/-1301123243"
photoUri = Uri.parse(photoUri.toString() + ".jpg");
// then I add .jpg to file name
// Create intent
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_STREAM, photoUri);
intent.setType("image/jpeg");
// Grant permissions to all apps that can handle this intent
// thanks to this answer http://stackoverflow.com/a/18332000
List<ResolveInfo> resInfoList = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
grantUriPermission(packageName, photoUri,
Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
// And start
startActivity(Intent.createChooser(intent, "Share image"));
However, depending on the app I'm getting strange exceptions. Like this:
1974-1974/com.whatsapp W/Bundle﹕ Key android.intent.extra.STREAM expected ArrayList but value was a android.net.Uri$HierarchicalUri. The default value <null> was returned.
1974-1974/com.whatsapp W/Bundle﹕ Attempt to cast generated internal exception:
java.lang.ClassCastException: android.net.Uri$HierarchicalUri cannot be cast to java.util.ArrayList
at android.os.Bundle.getParcelableArrayList(Bundle.java:1223)
at android.content.Intent.getParcelableArrayListExtra(Intent.java:4425)
at com.whatsapp.ContactPicker.d(ContactPicker.java:320)
at com.whatsapp.ContactPicker.onCreate(ContactPicker.java:306)
at android.app.Activity.performCreate(Activity.java:5104)
Or I just see logs like this:
591-963/system_process W/ActivityManager﹕ Permission denied: checkComponentPermission()
And of course apps themselves give me toasts with errors (that they can't open or download the file).
I tried, googled, stackoverflowed A LOT, so now I'm posting here.
I guess I'm doing it right, maybe just some little thing is wrong...
Any help will be appreciated!
I think this might be an an issue with either the FileProvider infrastructure or the application you're sharing with.
I have been trying to use this this support to share images w/ other applications. It works fine w/ Gallery, Drive, Gmail and Keep. It fails with Photos and Google+.
I see entries like the following in LogCat which leads me to believe that the Uri is being passed from one Activity to another. However, the (temporary) permission set in the Intent is being lost.
11-19 21:14:06.031: I/ActivityManager(433): Displayed com.google.android.apps.plus/.phone.HostPhotoViewIntentActivity: +848ms

Categories

Resources