2

I set a preference for users to select save folder for my app using Storage Access Framework. After getting uri onActivityResult I save it to SharedPreferences as String and save image when it's to be saved.

I'm using this method to save image successfully.

public void saveImageWithDocumentFile(String uriString, String mimeType, String name) {
    isImageSaved = false;
    try {
        Uri uri = Uri.parse(uriString);
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, uri);
        DocumentFile file = pickedDir.createFile(mimeType, name);
        OutputStream out = getContentResolver().openOutputStream(file.getUri());
        isImageSaved = mBitmap.compress(CompressFormat.JPEG, 100, out);
        out.close();

    } catch (IOException e) {
        throw new RuntimeException("Something went wrong : " + e.getMessage(), e);
    }
    Toast.makeText(MainActivity.this, "Image saved  " + isImageSaved, Toast.LENGTH_SHORT).show();
}

If user deletes the folder selected using SAF, iget null DocumentFile file and app crashes. My first question is how can i check if folder exist without opening SAF ui again.

I also want to use ParcelFileDescriptor to save same image with this method

public void saveImageWithParcelFileDescriptor(String folder, String name) {
    if (mBitmap == null) {
        Toast.makeText(MainActivity.this, "", Toast.LENGTH_SHORT).show();
        return;
    }
    String image = folder + File.separator + name + ".jpg";
    Toast.makeText(MainActivity.this, "image " + image, Toast.LENGTH_LONG).show();

    Uri uri = Uri.parse(image);
    ParcelFileDescriptor pfd = null;
    FileOutputStream fileOutputStream = null;
    try {
        pfd = getContentResolver().openFileDescriptor(uri, "w");
        fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
        mBitmap.compress(CompressFormat.JPEG, 100, fileOutputStream);

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (pfd != null) {
            try {
                pfd.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (fileOutputStream != null) {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    Toast.makeText(MainActivity.this, "Image saved  " + isImageSaved, Toast.LENGTH_SHORT).show();

}

i get java.lang.IllegalArgumentException: Invalid URI: content://com.android.externalstorage.documents/tree/612E-B7BF%3Aname/imagePFD.jpg on linepfd = getContentResolver().openFileDescriptor(uri, "w");

This is how I call this method, currentFolder is the folder I get using intent and same folder on first method.

saveImageWithParcelFileDescriptor(currentFolder, "imagePFD");

How can I fix this, and which method is more preferable and why?

Thracian
  • 43,021
  • 16
  • 133
  • 222

1 Answers1

1

how can i check if folder exist

DocumentFile has an exists() method. In theory, pickedDir.exists() should tell you if it exists. In practice, it is up to the storage provider.

I also want to use ParcelFileDescriptor to save same image with this method

That code has bugs:

  • You cannot grant yourself permissions using grantUriPermission()

  • You cannot invent arbitrary Uri values via concatenation, particularly for a provider that is not yours

How can I fix this

Delete it.

which method is more preferable

The first one.

and why?

The first one will work, perhaps with minor adjustment. The second one has no chance of working.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Yes, the first one works as long as saved folder exist. I will check if folder exists with documentFile.exist(). Isn't there no way to save an image with custom name and ParcelFileDescriptor? – Thracian Aug 28 '17 at 18:23
  • 1
    @FatihOzcan: You are welcome to replace `OutputStream out = getContentResolver().openOutputStream(file.getUri())` from your first code snippet with `getContentResolver().openFileDescriptor(file.getUri(), "w")` and go with a `ParcelFileDescriptor` that way. I don't know what the advantage would be, the result would be much longer than your first code snippet, and the result would not closely resemble your second code snippet. The key is using `createFile()`, so the storage provider is the one coming up with the `Uri` that you write to. – CommonsWare Aug 28 '17 at 18:27
  • i removed `grantUriPermission()`. Getting permission or persistable permission is only valid for uri you get on `onActivityResult`? There are no info about this on documents. On documents "The most recent URIs that your app accessed may no longer be valid—another app may have deleted or modified a document. Thus, you should always call getContentResolver().takePersistableUriPermission() to check for the freshest data.", but how can i check if a folder exist with this method. It does not return anything. – Thracian Aug 29 '17 at 08:10
  • I also have another question. Isn't there no way to write a custom file with ParceFileDescriptor? It's not possible to save a bitmap to device or sd card without using a OutputStream. Is ParcelFileDescriptor for writing custom files? I didn't get how to exchange OutputStrem with ParcelFileDescriptor on second method – Thracian Aug 29 '17 at 08:20
  • @FatihOzcan: "Getting permission or persistable permission is only valid for uri you get on onActivityResult?" -- you can call `takePersistableUriPermission()` on `Uri` values delivered by Storage Access Framework actions to `onActivityResult()`. You should be able to use it also with `Uri` values returned by `DocumentFile` (e.g., `createFile()`), though I have not tried that. "how can i check if a folder exist with this method" -- it will throw a `SecurityException`, IIRC. – CommonsWare Aug 29 '17 at 10:41
  • @FatihOzcan: "Is ParcelFileDescriptor for writing custom files?" -- `ParcelFileDescriptor` is primarily there to allow you to open streams. `ContentResolver` handles that for you. A few other places in the framework take a `ParcelFileDescriptor` or the underlying file descriptor, but these tend to be fairly low-level operations. – CommonsWare Aug 29 '17 at 10:42