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.
Related
Not a duplicate to Choosing photo using new Google Photos app is broken
My app requests for file which can be images or videos and we only support some of the file extensions. And since MediaStore.MediaColumns.Data is deprecated, I've to go with the fileDescriptor approach to solve this. But I'm not able to figure out a way to get the extension of selected file.
The URI I get after selecting a video from the Google Photos app looks like this:
content://com.google.android.apps.photos.contentprovider/-1/2/content%3A%2F%2Fmedia%2Fexternal%2Fvideo%2Fmedia%2F25/ORIGINAL/NONE/643532680
When I try to query ContentResolver using _data column it gives an IllegalArgumentException to me, so I used below code to sort of copy the file to a temporary location.
String[] str = uri.toString().split("/");
String fileName = str[str.length - 1];
String filePath = context.getFilesDir().getAbsoluteFile() + File.separator + fileName;
File newFile = new File(filePath);
FileDescriptor fileDescriptor = context.getContentResolver().openFileDescriptor(uri, "r").getFileDescriptor();
FileInputStream fis = new FileInputStream(fileDescriptor);
copyFile(fis, newFile);
return filePath;
So, how should I get the correct mimeType of the selected file?
PS: I tried MimeTypeMap, ContentResolver.getType but none of them works.
EDIT:
Adding more details:
I'm using api level 29. Running the app on a Nexus 6(API 29) emulator. And simply calling ACTION_GET_CONTENT to allow user to select an image/video. Also included the URI I get after selecting the photo which doesn't contain mimetype(which as suggested by CommansWare is normal). When I call ContentResolver.getType on the above URI I get null.
EDIT 2:
Minimal code to reproduce:
Started an intent like this:
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
i.setType("video/*");
startActivityForResult(i,IMAGE_VIDEO_FETCH);
Then in the onActivityResult, checked the mimetype.
Uri media = intent.getData();
this.getContentResolver().getType(media); /// Here it returns null.
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.
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.
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
Suppose I'm selecting a file externally via:
Intent i = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, GALLERY_REQUEST_CODE);
and in the result is returned via onActivityResult
Uri file = data.getData();
This can be in different formats. What is the convention here to determine if a content resolver query is needed? If the file URI is a file path file:/// or a app URI like //package.example/....
I know I could check if the uri has a file:/// prefix but that seems a bit hacky. I just want the file path.
What is the convention here to determine if a content resolver query is needed?
openInputStream() on ContentResolver handles both the file scheme and the content scheme.
I know I could check if the uri has a file:/// prefix but that seems a bit hacky
Um, well, Uri has a getScheme() method.
I just want the file path.
There is no "file path" for a content Uri, as there is no requirement that the content be stored as a file.
Just to compliment CommonsWare answer. You can examine the scheme of the URI, using, for example, getScheme() method, as mentioned in CommonsWare's answer. A content provider URI scheme is content://, while a file URI scheme is file://