I am creating an android application using xamarin.android with targetsdk is Marshmallow.
I am using SAF for writing to external sd card (removable media), for this I call an Intent with ACTION_OPEN_DOCUMENT_TREE
var intent = new Intent(Intent.ActionOpenDocumentTree);
StartActivityForResult(intent, ACTION_OPEN_DOCUMENT_TREE);
I had saved that tree uri on SharedPreference for long term permission until my device gets rebooted like
treeUri.ToString()
and read that uri like
var str = Android.Net.Uri.Decode(UriString);
var TreeUri = Android.Net.Uri.Parse(str);
And parse this TreeUri to DocumentFile class to manage documents but sometimes in between it missed some part of uri and I need to invoke it again to user to grant permission for managing documents and save the treeuri again.
As we can see this on ES File Explorer that it also ask the user to grant the permission and it maintains the treeuri until the device reboots.
Here is the image of ES File Explorer
Do correct me what to do to maintain tree uri until device reboots.
Edit:
Here is the TakePersistableUriPermission code:
var takeFlags = ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission;
GrantUriPermission(PackageName, data.Data, takeFlags);
ContentResolver.TakePersistableUriPermission(data.Data, takeFlags);
Related
I am trying to upgrade an app to be copliant with the Android11 shared storage concept. This app needs to store its created gpx files in a storage area not destroyed in case the app is uninstalled. This area shall stay in shared storage and the directory can be created with ACTION_CREATE_DOCUMENT:
Intent intent = new Intent(ACTION_CREATE_DOCUMENT);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
intent.setType(DocumentsContract.Document.MIME_TYPE_DIR);
intent.putExtra(Intent.EXTRA_TITLE, "AGPS-Tracker");
startActivityForResult(intent, SS_CREATE_GPX_DIR_REQUEST);
and in onActivityResult:
Uri uri = data.getData();
final int takeFlags = data.getFlags()
& (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
startDirUri = DocumentsContract.buildTreeDocumentUri(uri.getAuthority(),DocumentsContract.getDocumentId(us));
Then I try to use startDirUri :
uriDir = DocumentsContract.buildDocumentUriUsingTree(startDirUri, DocumentsContract.getTreeDocumentId(startDirUri));
I get: java.lang.SecurityException: Permission Denial: reading com.android.externalstorage.ExternalStorageProvider uri content://com.android.externalstorage.documents/tree/primary%3AAGPS-Tracker%20(11)/document/primary%3AAGPS-Tracker%20(11)/children from pid=7209, uid=10341 requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
Then if I use ACTION_OPEN_DOCUMENT_TREE on this new created directory, finally I get the access.
My question is: why a directory created by the user can't be immediately used? Really we need to ask twice the user: first to create and then to use the same directory?
I want to write on sdcard (External sdcard) on android +6.
When I use this runtime permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
It just works for internal storage but on sdcard i get:
java.io.IOException: Permission denied
How can other apps write on sdcard?
Thanks.
Solved
1. in android +6 the Runtime Permissions just work for Device Storage and not for Sd Card (External SD)
2.For External SD you must Use SAF (Storage Access Framwork) and select sdCard Directory
via this Code you can run SAF (Storage Access Framwork)
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), 42);
After select the sdcrd directory the onActivityResult method was run
#Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (resultCode != RESULT_OK)
return;
else {
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
preferences = getSharedPreferences("MyShared" , MODE_PRIVATE) ;
SharedPreferences.Editor editor = preferences.edit() ;
editor.putString("Dir" , treeUri.toString());
editor.commit() ;
}
}
the SAF return intent of selected Directory
and we get intent data as a Intent treeUri
(Very Important)→→ for write on sd card in android 6+ you must use From DocumentFile instead of File
and DocumentFile get sd card Directory
Dont Forget to save treeUri in the SharedPreferences , Because you need to save the Directory of Sd Card and every time you need can get it from SharedPreferences
and now pickedDir is the Sdcard directory , you can use it
For API 23 and above, you need to explicitly request permission from the user AND include the uses permission tag in your android manifest: https://developer.android.com/training/permissions/requesting.html
USE this library to get permission in android phone
https://github.com/nabinbhandari/Android-Permissions
Also see this link you get possible information for the solution
Storage permission error in Marshmallow
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.
I'm trying to delete an audio file in the SD-card but I'm not successful
public boolean delete(String path)
{
return new File(path).delete();
}
While going through posts I came across Storage Access Framework but unable to understand. Is it required for deleting files from SD-card?
Moreover Can I only use Content Resolver to delete Files Like this?
getContenResolver().delete(uri,null,null);
Or is there any other method for deleting Audio?
My app is well elevated with Write permission and Read permission and I am testing on Marshmallow 6.0
Please answer.
Thanks in advance.
You need to ensure two things:
1) Since you are targeting Android.M you need to get permissions on runtime as well. Simply asking for them in the Manifest is not enough.
2) if you want to read/write data on SD card you need to use DocumentFile instead of File. The logic is more or less the same but you can refer here for more info: https://developer.android.com/reference/android/support/v4/provider/DocumentFile.html
Use this command in your OnCreate or anywhere you wish, to open a dialog that let's you select an SD directory. Select the one where your image is.
startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), 42);
Then you will need this method as is:
#Override
public void onActivityResult(int requestCode,int resultCode,Intent resultData) {
if (resultCode != RESULT_OK)
return;
Uri treeUri = resultData.getData();
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
DocumentFile YourAudioFile= pickedDir.findFile("YourAudioFileNameGoesHere");
// And here you can delete YourAudioFile or do whatever you want with it
}
I am trying to follow this tutorial to invoke the camera app and ask it to save a picture at the uri passed in the intent.
File image = File.createTempFile("testImage", ".jpg",
getExternalFilesDir(Environment.DIRECTORY_PICTURES));
Uri uri = Uri.fromFile(image);
Intent i = new Intent();
i.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
i.putExtra(MediaStore.EXTRA_OUTPUT, uri);
if (i.resolveActivity(getPackageManager()) != null) {
startActivityForResult(i, 1);
}
The path of the image is :
/storage/emulated/0/Android/data/com.android.test1.app/files/Pictures/testImage-516714791.jpg
I want to ask that how did the camera app have permission to write to this path ? I am testing on Android 4.4, so the path ExternalFilesDir is not publicly writable.
The reference says:
There is no security enforced with these files. For example, any application holding WRITE_EXTERNAL_STORAGE can write to these files.
Starting in KITKAT, no permissions are required to read or write to the returned path; it's always accessible to the calling app. This only applies to paths generated for package name of the calling application. To access paths belonging to other packages, WRITE_EXTERNAL_STORAGE and/or READ_EXTERNAL_STORAGE are required.
So, system Camera app, or any other app which is granted WRITE_EXTERNAL_STORAGE permission can write to /storage/emulated/0/Android/data/com.android.test1.app/files/Pictures/.