Thank you so much for reading this question.
Currently I am about to finish my project.
However I am in a big trouble.
Although I have achieved to gain permission of writing on SD card in my app,
I cannot even google or figure out how to use this permission properly.
Does anyone know how to write file on SD card via SAF->channelSftp.get()?
Therefore, I can write on External Storage properly, but not on SD card.
Here are my code which I used to gain access to SD card.
Hope someone can help me out with a solution.
This Code is used to gain access to SD card:
public void takeCardUriPermission(String sdCardRootPath) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
File sdCard = new File(sdCardRootPath);
StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
StorageVolume storageVolume = storageManager.getStorageVolume(sdCard);
Intent intent = storageVolume.createAccessIntent(null);
try {
startActivityForResult(intent, 4010);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
}
}
This Code is used to keep permission persistent:
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 4010) {
Uri uri = data.getData();
grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
}
}
And This is How I implemented the permission and which keep occurs permission denied error
void write(String src, Uri uri){
try {
ParcelFileDescriptor descriptor=context.getContentResolver().openFileDescriptor(uri,"w");
if(descriptor!=null) {
FileOutputStream fos=new FileOutputStream(descriptor.getFileDescriptor());
channelSftp.get(src,fos);
fos.close();
}
} catch (IOException | SftpException e) {
Log.e("Error", "again "+e.getMessage());
}
}
Again, Thank you so much for reading this and hope I get an answer someday.
Related
In an Android application,
And Android 10 regarding Scoped Storage,
Picasso is able to truly display an image with the Uri of an image
And it does not require any permission even if the versin of Android is 10
But when I try to use openInputStream, an error occurs which indicates permission is not granted
I wonder how Picasso can display the image without any permission but opening stream requires permission
The code is as following:
Picasso.with(mContext).load(mUri).resize(200, 200).into(mImageButton);
InputStream mInputStream;
try
{
mInputStream = mContext.getContentResolver().openInputStream(mUri);
} catch (Exception e) {
e.printStackTrace();
}
And the error is as following:
java.lang.SecurityException: Permission Denial: reading com.miui.gallery.provider.GalleryOpenProvider uri content://com.miui.gallery.open/raw/storage/emulated/0/Download/MellatMobileBank_Android.apk/icons/jar-gray.png from pid=2300, uid=10251 requires the provider be exported, or grantUriPermission()
Then, how can I have the stream of the image without granting permission?
Edit:
I did a test in onActivityResult where the image result could be retrieved from to see if the openInputStream works there or not
The code is as following:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Constants.Uri_M) {
if (resultCode == RESULT_OK) {
Uri_M = data.getData();
Uri_M_String = Uri_M.toString();
mInputStream = getApplicationContext().getContentResolver().openInputStream(Uri_M);
Above code works fine while the following code has the same previous error (permission issue):
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == Constants.Uri_M) {
if (resultCode == RESULT_OK) {
Uri_M = data.getData();
Uri_M_String = Uri_M.toString();
Uri b = Uri.parse(Uri.decode(Uri_M_String));
mInputStream = getApplicationContext().getContentResolver().openInputStream(b);
The point is Picasso is working fine with Uri_M_String but for openInputStream I first convert the Uri_M_String to Uri_M as following:
Uri Uri_M = Uri.parse(Uri.decode(Uri_M_String));
And then:
mInputStream = getApplicationContext().getContentResolver().openInputStream(Uri_M);
Which results in the error
So the problem is the conversion I'm doing to get the Uri from Uri string
But how can I reach the true Uri from its Uri string?
I am trying to write to a file that is located in the SDCard, I found out that I need special permission for removable storage something that is not found in any known permission handler plugin for flutter (i tried simple_permission and permission_handler with no use).
I tried to acquire those permissions using the android side of things, so I wrote a simple function that would show the dialog and the user would allow the app to modify the content of the SDCard.
even after acquiring the rights to the SDCARD, I still get the same permissions denied error when trying to save files to the SDCard when using File.writeAsStringSync method.
I want to know if there is any known way/hack/workaround to save files in SDCards in flutter.
The android code i used is the same from this answer : https://stackoverflow.com/a/55024683/6641693
NOTE : I am targetting android 7 and beyond but not android 11.
I solved This, by ditching the dart file saving and using the android SAF.
First, what I did was try to get the sdCard modification permissions.
After that, I get to save the files I need.
here is the code I used to get the permissions ( aka the "allow this app to modify content on your sdCard" dialog )
public void takeCardUriPermission(String sdCardRootPath) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
File sdCard = new File(sdCardRootPath);
StorageManager storageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
StorageVolume storageVolume = storageManager.getStorageVolume(sdCard);
Intent intent = storageVolume.createAccessIntent(null);
try {
startActivityForResult(intent, 4010);
} catch (ActivityNotFoundException e) {
Log.e("TUNE-IN ANDROID", "takeCardUriPermission: "+e);
}
}
}
protected void onActivityResult(int requestCode, int resultCode, #Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 4010) {
Uri uri = data.getData();
grantUriPermission(getPackageName(), uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
final int takeFlags = data.getFlags() & (Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION);
getContentResolver().takePersistableUriPermission(uri, takeFlags);
methodChannel.invokeMethod("resolveWithSDCardUri",getUri().toString());
}
}
public Uri getUri() {
List<UriPermission> persistedUriPermissions = getContentResolver().getPersistedUriPermissions();
if (persistedUriPermissions.size() > 0) {
UriPermission uriPermission = persistedUriPermissions.get(0);
return uriPermission.getUri();
}
return null;
}
So in order to start the whole permissions acquiring process, you have to first call takeCardUriPermission and passing the URI of the sdCard path.
Note: on my FlutterActivity, i am able to get the sdCardPath directly using getExternalCacheDirs()[1].toString()
After calling takeCardUriPermission and once the allow button is pressed (or the decline) an activity result event will be called and the onActivtyResult method will be called. the requestCode check is useful when you have multiple events and you need to filter this one out.
The activity result code will give the app permissions to modify the files on the sdCard.
The getUri method is the one that we will be using afterwards when trying to save bytes to a file, it returns the URI of the SDCard that we selected (you can have multiple sdCards).
Saving Files
What I used to save a file is a straightforward method. First we need to get the URI of the sdCard and create a Documentfile out of it, then we go through the hierarchy of that directory (DocumentFile can reference files and directories) to find the needed file based on it's URI.
We do this search by splitting the file URI into parts and then navigating the hierarchy by testing if each part exists or not. Once we test all the parts we would have reached our file, if it exists, or we were stuck at the last directory we got to.
the resulting of this iteration is a DocumentFile that we can execute operations on and with.
the following is the full file saving code :
String filepath = (String) arguments.get("filepath");
final byte[] bytes = methodCall.argument("bytes");
try{
if(filepath==null || bytes==null)throw new Exception("Arguments Not found");
DocumentFile documentFile = DocumentFile.fromTreeUri(getApplicationContext(), getUri());
String[] parts = filepath.split("/");
for (int i = 0; i < parts.length; i++) {
DocumentFile nextfile = documentFile.findFile(parts[i]);
if(nextfile!=null){
documentFile=nextfile;
}
}
if(documentFile!=null && documentFile.isFile()){
OutputStream out = getContentResolver().openOutputStream(documentFile.getUri());
out.write(bytes);
out.close();
}else{
throw new Exception("File Not Found");
}
}catch (Exception e){
result.error("400",e.getMessage(),e);
return;
}
result.success(true);
Note: in my code, I am calling this under the MethodChannel's MethodCallHandler which will give me the argument I need: filePath which is the String URI of the file I want to write to and the bytes byte array representing the data I want to save. The same can be said for the result.success
The file writing code is simple: open the file, write the data and close the file.
I created an app a few years ago which had it's own file explorer and does so many processes on selected files. recently I wanted to add ability to write to external storages like SD Cards and Hard drives connected through OTG.
The problem is I can't rewrite the whole project based on DocumentFile structure since it's a very big project and it would take forever for me to update. So I needed to somehow convert the old File methods in a few lines and be done with it. this is what I've added so far :
private void getPersistentPermission() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra("android.content.extra.SHOW_ADVANCED", true);
startActivityForResult(intent, reqcode_storage);
}
and :
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == reqcode_storage) {
Uri uri = data.getData();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
final int perm_flag = Intent.FLAG_GRANT_READ_URI_PERMISSION |
Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
grantUriPermission(getPackageName(), uri, perm_flag);
getContentResolver().takePersistableUriPermission(uri, perm_flag);
}
}
} else {
Log.d(tag, "something`s wrong !");
}
}
then I try to do something like this :
try {
DocumentFile dfile = DocumentFile.fromFile(file);
OutputStream os = getContentResolver().openOutputStream(dfile.getUri());
os.write(some_string.getBytes());
} catch (Exception e) {
Log.e(tag, e.toString());
} finally {
try {
os.close();
} catch (Exception ignored) {}
}
I get the "file" using implemented file explorer inside my app. it works for internal storage.
I read so many threads about SAF but somehow I can't get it to work. it always shows this error :
java.io.FileNotFoundException: Permission denied
Can anyone tell me what am I missing here ?
thanks in advance.
I am trying to create a file on sd-card on lollipop device. I am aware of ACTION_OPEN_DOCUMENT_TREE, and how to get permission for root of sd card.
What I want to achieve is this:
in my own folder browser, user picks a folder (on sdcard) where he wants file to be created (for example "/storage/emulated/0/a/b/c/d")
first time this happens, I use ACTION_OPEN_DOCUMENT_TREE, and then in onActivityResult I use findFile to create file in correct location:
next time user picks a folder on sd-card, he does not need to use ACTION_OPEN_DOCUMENT_TREE
code:
public void test()
{
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, 1);
}
public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
if (resultCode == RESULT_OK)
{
Uri treeUri = resultData.getData();
final int takeFlags = resultData.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
//assuming he picked "/storage/emulated/0/a/b/c/d"
DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
DocumentFile a = pickedDir.findFile("a");
DocumentFile b = a.findFile("b");
DocumentFile c = b.findFile("c");
DocumentFile d = c.findFile("d");
DocumentFile newFile = d.createFile("text/plain", "somefile.txt");
OutputStream out;
try
{
out = getContentResolver().openOutputStream(newFile.getUri());
out.write("A long time ago...".getBytes());
out.close();
}
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
The question is how do I know in onActivityResult that user actually picked root of sdcard? He could have picked /storage/emulated/0/a1/a2, and if that folder has subfolders a/b/c/d, I would create file in wrong folder (because findFile("a");findFile("b"); etc.. would also succeed).
Also, next time user picks a folder (with my own folder picker), I get path, not Uri, how do I translate that path to Uri which can be used with DocumentFile?
You still have read access to the removable external storage using File. So you can create a temporary file with a unique name in the directory of the Uri that came back from ACTION_OPEN_DOCUMENT_TREE, using DocumentFile. and Uri. Then check if it is where you think it should be using File and the path. The following code returns true if myPath matches treeUri.
String myPath = "/storage/sdcard1/Podcasts";
Uri treeUri = resultIntent.getData();
int i = 0;
File f;
String s;
while ((f = new File(myPath + (s = "/tmp" + i + ".mp3"))).exists()) ++i;
final DocumentFile d = treeDir.createFile("audio/mp3", s);
if (d == null) return false;
try {
OutputStream str = getContentResolver().openOutputStream(d.getUri());
str.write(new byte[10]);
str.close();
} catch (IOException e) {
return false;
}
final boolean fExists = f.exists();
d.delete();
return fExists;
I have a web service which give me a byte[] array according to image id . I want to convert these byte[] to file and store a file on android where user want like save file dialog box with file same format exactly it has.
Since this is the top result in google when you search for that topic and it confused me a lot when I researched it, I thought I add an update to this question.
Since Android 19 there IS a built in save dialog. You dont event need any permission to do it (not even WRITE_EXTERNAL_STORAGE).
The way it works is pretty simple:
//send an ACTION_CREATE_DOCUMENT intent to the system. It will open a dialog where the user can choose a location and a filename
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("YOUR FILETYPE"); //not needed, but maybe usefull
intent.putExtra(Intent.EXTRA_TITLE, "YOUR FILENAME"); //not needed, but maybe usefull
startActivityForResult(intent, SOME_INTEGER);
...
//after the user has selected a location you get an uri where you can write your data to:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == SOME_INTEGER && resultCode == Activity.RESULT_OK) {
Uri uri = data.getData();
//just as an example, I am writing a String to the Uri I received from the user:
try {
OutputStream output = getContext().getContentResolver().openOutputStream(uri);
output.write(SOME_CONTENT.getBytes());
output.flush();
output.close();
}
catch(IOException e) {
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show();
}
}
}
More here:
https://developer.android.com/guide/topics/providers/document-provider
The Android SDK does not provide its own file dialog, therefore you have to build your own.
You cant create a save file dialog but you can save files from ur application to android sd card with the help of below links
http://android-er.blogspot.com/2010/07/save-file-to-sd-card.html
http://www.blackmoonit.com/android/filebrowser/intents#intent.pick_file.new
First, you should create a dialog intent for saving the file, After selection by the user, you can write on that directory and specified the file without any read/write permissions. ( Since Android 19 )
Source:https://developer.android.com/training/data-storage/shared/documents-files#create-file
// Request code for creating a PDF document.
private final int SAVE_DOCUMENT_REQUEST_CODE = 0x445;
private File targetFile;
private void createFile() {
Uri reportFileUri = FileProvider.getUriForFile(getApplicationContext(), getPackageName() + ".provider", targetFile);
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("application/pdf");
intent.putExtra(Intent.EXTRA_TITLE, targetFile.getName());
// Optionally, specify a URI for the directory that should be opened in
// the system file picker when your app creates the document.
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
startActivityForResult(intent, SAVE_DOCUMENT_REQUEST_CODE );
}
#Override
protected void onActivityResult(int requestCode, int resultCode, #Nullable
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SAVE_DOCUMENT_REQUEST_CODE && resultCode == RESULT_OK){
Uri uri = data.getData();
saveFile(uri);
}
}
private void saveFile(Uri uri) {
try {
OutputStream output = getContentResolver().openOutputStream(uri);
FileInputStream fileInputStream = new FileInputStream(targetFile);
byte[] bytes = new byte[(int) targetFile.length()];
fileInputStream.read(bytes, 0, bytes.length);
output.write(bytes);
output.flush();
output.close();
Log.i(TAG, "done");
} catch (IOException e) {
Log.e(TAG, "onActivityResult: ", e);
}
}
#JodliDev already provided the accepted answer, however, startActivityForResult is now deprecated, so I want to provide my solution here using registerForActivityResult(ActivityResultContracts.CreateDocument())
First register a ActivityResultLauncher where you define what should happen with the result. We'll get the uri back that we can use for our OutpuStream. But make sure to initialize it at the beginning, otherwise you will get:
Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()).
private var ics: String? = null
private val getFileUriForSavingICS = registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
if(ics.isNullOrEmpty())
return#registerForActivityResult
try {
val output: OutputStream? =
context?.contentResolver?.openOutputStream(uri)
output?.write(ics?.toByteArray())
output?.flush()
output?.close()
} catch (e: IOException) {
Toast.makeText(context, "Error", Toast.LENGTH_SHORT).show()
}
}
Then just call your ActivityResultLauncher with .launch(...) wherever it is needed.
getFileUriForSavingICS.launch("filename.txt")
And that's about it ;-)
You can also have a closer look at ActivityResultContracts.CreateDocument(). This method provides the document saving dialog, but there are other helpful functions inside (like for starting a camera intent). Check out:
https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts
for the possible ActivityResultContracts
Or https://developer.android.com/training/basics/intents/result for some more training material and also some information how a custom contract could be created!