Is it possible to reuse a content sheme URI - android

When opening a file from a file explorer I get a content scheme URI like following:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk
I then make a temporary copy of the file using the content resolver using something like following:
File tempFile = getFileFromContentUri(getContext(), mUri, null);
After the app processed tempFile this file gets deleted. The problem is now that I want to forward mUri to another activity with following code, but I am getting a security exception while doing so, so it seems that the URI can only be used once, is this right?:
private forwardFile(Uri fileUri) {
final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
Uri uri;
if (ContentResolver.SCHEME_CONTENT.equals(fileUri.getScheme())) {
uri = new Uri.Builder()
.path(fileUri.getPath())
.authority(fileUri.getAuthority())
.scheme(ContentResolver.SCHEME_CONTENT)
.build();
}
installIntent.setData(uri);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.startActivity(installIntent);
}
Is there the possibility to reuse the uri or a workaorund that you can see? I'm not seeing a way I can handle this, e.g. I must delete the temp file, but if I forward the copied file instead of the original URI I don't get a callback so I wouldn't know when to delete the copied file.
And here the exception I am getting:
java.lang.SecurityException: Uid 10165 does not have permission to uri 0 # content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk

Only if the serving app defines a persistent permission and the client calls context.getContentResolver().takePersistableUriPermission() this may work, but I am also not sure.

You got the permission for:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/backups/apps/testfile.apk
No wonder that you got a java.lang.SecurityException: using a completely different (and non existing) file:
content://com.asus.filemanager.OpenFileProvider/file/sdcard/Download/testfile.apk

Related

How can I use file Uri from another app Intent in Android

In my APP I received the Uri of a pdf file from any other app that can share pdfs.
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
Anyway I cannot full-use this Uri, I image because of it is from a FileProvider external to my app.
For example when I try to create a FileDescriptor:
ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(uri, "r");
I get this error:
W/System.err: java.lang.SecurityException: Permission Denial: opening provider androidx.core.content.FileProvider from ProcessRecord{...} (pid=23606, uid=10525) that is not exported from UID 10123
It is really necessary get the file path, copy the file, and get a new Uri?
Or I can simply manage the original Uri in some way ?
As per my knowledge the best thing to do is to read/copy the data from content provider immediately and then use that data to do whatever you want. so don't waste your time trying to manage the original one. I hope its helpful to you

Unable to get a File handle for a URI that starts with "content://0#media/"

In order to access a video and share it via our app, I am trying to access it using a FileProvider. The code works fine for all URIs except the one starting with "content://0#media/". In this specific case, the check "vidFile.exists()" returns false. Please let me know how to access files that have such content URI. Appreciate your help.
File vidFile = new File(uri.getPath());
if (vidFile.exists()) //This is returning false for this content URI
{
Uri vidUri = FileProvider.getUriForFile(
context,
context.getString(R.string.file_provider_authority),
vidFile);
}
I used getContentResolver().query and I could get the video attributes. No need to use FileProvider as CommonsWare commented.

Opening file from SD card via Intent.ACTION_VIEW

I know that we can open a file normally from internal storage like this:
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath(), "my_file.zip");
Uri uri = (FileProvider.getUriForFile(context, AUTHORITY_OPEN_FILE, file)
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(uri);
context.startActivity(intent);
But I do not know on how to open a file from SD card, which we can pick using DocumentFile like this:
Uri uri = new Uri.Builder()
.scheme("content")
.authority("com.android.externalstorage.documents")
.appendPath("document")
.appendPath(directory)
.appendPath(fileName)
.build();
DocumentFile documentFile = DocumentFile.fromSingleUri(context, uri);
I tried the following snippet:
Uri uri = documentFile.getUri();
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(uri);
context.startActivity(intent);
But resulting error:
Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=android.intent.action.VIEW dat=content://com.android.externalstorage.documents/document/6331-6132:/haxm-windows_r05_2.zip flg=0x10000001 cmp=com.google.android.gm/.browse.TrampolineActivity } from ProcessRecord{a0ed6d5 9894:com.mypackage.app/u0a169} (pid=9894, uid=10169) requires com.google.android.gm.permission.READ_GMAIL
I did grant for read and write external storage permissions:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And Uri permission as well:
int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
context.getContentResolver().takePersistableUriPermission(uri, takeFlags);
A file stored in SD card gives us a Uri like this, where 6331-6132 is identifier for our removable SD card:
content://com.android.externalstorage.documents/document/6331-6132:Folder/haxm-windows_r05_2.zip
I read so many posts on StackOverflow, but nothing help. Can you help me to solve this error? Thanks in advance.
yo must add permission in manifest file and for marshmallow or above version of android you add run time permission.
for this view
https://www.simplifiedcoding.net/android-marshmallow-permissions-example/
I get the Uri using Intent.ACTION_OPEN_DOCUMENT. Next, I save the Uri into my database. Finally, I reconstruct the Uri to get stream using getContentResolver().openInputStream(uri).
My guess is that you are not calling takePersistableUriPermission() on a ContentResolver in onActivityResult(), before you save the Uri to the database. Without this, you do not have long-term access to the content identified by the Uri.
This may be very basic but the error message says that you are missing permission
did you ask for READ_EXTERNAL_STORAGE ?
https://developer.android.com/training/data-storage/files#ExternalStoragePermissions
I finally found a solution for this. java.lang.SecurityException: Permission Denial will be thrown when you open a external file using DocumentFile.fromSingleUri(context, uri). This static method is only useful when you pick a file inside onActivityResult() method with action Intent.ACTION_OPEN_DOCUMENT or Intent.ACTION_CREATE_DOCUMENT. When your app is destroyed, the permission to access the file is gone. You should not use DocumentFile.fromSingleUri(context, uri) while working with files that require long time permission. Hence, we need DocumentFile.fromTreeUri(context, uri) instead.
To do that so, you must access the root of SD card's ID (e.g. 6331-6132) first. For example:
String path = "6331-6132:Video/Iykwim.mp4";
String sdcardId = path.substring(0, path.indexOf(':') + 1); // => returns '6331-6132:'
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(sdcardId));
DocumentFile root = DocumentFile.fromTreeUri(context, uri);
// Now, we already have the root of SD card.
// Next, we will get into => Video => Iykwim.mp4
DocumentFile file = root.findFile("Video").findFile("Iykwim.mp4");
Notice that you cannot access the file directly like this Uri:
String path = "6331-6132:Video/Iykwim.mp4";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(path));
DocumentFile file = DocumentFile.fromTreeUri(context, uri);
Or like this:
String folder = "6331-6132:Video";
Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + Uri.encode(folder));
DocumentFile directoryVideo = DocumentFile.fromTreeUri(context, uri);
DocumentFile file = directoryVideo.findFile("Iykwim.mp4");
Finally, you can open the file using Intent.ACTION_VIEW:
Intent intent = new Intent(Intent.ACTION_VIEW)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(documentFile.getUri());
context.startActivity(intent);
The worst scenario is when you call findFile() from a folder with so many files inside. It will take long time and may lead to crash your app. Ensure that you always call this method from different thread.
Android is getting worst day by day. They have made a Storage Access Framework (SAF) that makes you difficult to manage files on SD card. Using java.io.File is almost deprecated since Android Kitkat. You should use DocumentFile instead.

Android FileProvider.GetUriForFile is cutting off the start of the URI

I'm trying to update my code which gets the database file. I'm trying to use a FileProvider instead.
I'm a bit stuck because the original file path is "/storage/emulated/0/Android/data/com.abc/files/abcMobile.db3”
I need to get the URI which is returned by FileProvider.GetUriForFile to include "/storage/emulated/0/“ at the start of it.
Here is my code:
Log.Info ("directory", GetDirectory (dir).ToString());
Log.Info ("filename", filename);
File newFile = new File (GetDirectory (dir).AbsolutePath, "/" + filename);
Android.Net.Uri androidContentUri = FileProvider.GetUriForFile (Application.Context, Application.Context.PackageName + ".fileprovider", newFile);
System.Uri systemContentUri = SystemUri (androidContentUri);
return systemContentUri;
In the code above I have created a file which has a path of "/storage/emulated/0/Android/data/com.abc/files/abcMobile.db3” but when I pass the file to FileProvider.GetUriForFile() It seems to create an Android.Net.URI of {content://com.abc.fileprovider/./Android/data/com.abc/files/abcMobile.db3} and I cast it to a System.Net.URI which becomes {content://com.abc.fileprovider/Android/data/com.abc/files/abcMobile.db3}.
I just need to figure out why it deletes "/storage/emulated/0/“ from the start of the Android.Net.Uri and stop that from happening and then it should work. Any ideas?
I just need to figure out why it deletes "/storage/emulated/0/“ from the start of the Android.Net.Uri and stop that from happening and then it should work. Any ideas?
Because you are generating content uri for your file,which grant temporary permission for access to this file. Return a full path of the file is not considered to be a secure way.
And if you are refer to "Generating the Content URI for a File" section of FileProvider. You will find the following sentence:
To share a file with another app using a content URI, your app has to generate the content URI.
...
As a result of the previous snippet, getUriForFile() returns the content URI content://com.mydomain.fileprovider/my_images/default_image.jpg.
So, it is a expected form of ContentUri that returned by FileProvider.

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