0

My app is migrating to SAF or, at least some experimentation is going about.

Now it has to copy a file from private app folder to a SAF folder that was authorized.

The used method is:

 static boolean copyFileToTargetFolderWithNewName(Activity activity, String filePath,String targetFolderUri,String newName)
{
    File file = new File(filePath);

    FileInputStream fis=null;
    Uri docUri=null;
    try {
        fis=new FileInputStream(file);
    } catch (FileNotFoundException e) {
    return false;
    }

    deleteIfExisting(activity,Uri.parse(targetFolderUri),newName);


    ContentResolver resolver = activity.getContentResolver();
    boolean result=false;

    int offset=filePath.lastIndexOf(".");
    String ext="";
    if (offset!=-1) ext=filePath.substring(offset+1);
    String mimetype = android.webkit.MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
    try {
//error here
          docUri=DocumentsContract.createDocument(resolver,Uri.parse(targetFolderUri),mimetype,newName);
    } catch (FileNotFoundException e) {
        result=false;
    }

    try {
        ParcelFileDescriptor pfd=resolver.openFileDescriptor(docUri, "w");
        FileOutputStream fos=new FileOutputStream(pfd.getFileDescriptor());
        int b;
        while  ((b=fis.read()) != -1)
            fos.write(b);       
        fis.close();
        fos.close();
        pfd.close();
        result= true;
        } catch (FileNotFoundException e) {
        result=false;
        } catch (IOException e) {
        result=false;
        }
    return result;
}

I get

java.lang.IllegalArgumentException: Invalid URI: content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Ffolder/subfolder

the folder was created by means of this method:

static public DocumentFile createFolderInFolder(Activity activity,String parentFolderUriString,String folderName)
{
DocumentFile result=null;
ContentResolver contentResolver;
contentResolver = activity.getContentResolver();

Uri parentFolderUri=null;

Uri oldParentUri = Uri.parse(parentFolderUriString);
String id = DocumentsContract.getTreeDocumentId(oldParentUri );

parentFolderUri= DocumentsContract.buildChildDocumentsUriUsingTree(oldParentUri , id);

/*
    String id=DocumentsContract.getTreeDocumentId(Uri.parse(parentFolderUriString));

id=StringUtils.fromLastSlashRight(parentFolderUriString);
parentFolderUri= DocumentsContract.buildTreeDocumentUri(PROVIDER_AUTHORITY,id
        );
*/


DocumentFile parentFolder = DocumentFile.fromTreeUri(activity, parentFolderUri);
result=parentFolder.createDirectory(folderName);

/*try {
   result=DocumentsContract.createDocument(contentResolver,parentFolderUri,DocumentsContract.Document.MIME_TYPE_DIR,folderName);

} catch (FileNotFoundException e) {
    result =null;
}*/

return result;
}

as you can see, a previous version of the creation is commented because it did not work. It was necessary to use DocumentFile, but it seems that there are incompatibilities with DocumentsContract. Am I wrong?

So is SAF broken? Or am I circling around?

P5music
  • 3,197
  • 2
  • 32
  • 81
  • That `Uri` looks malformed, with a mix of URL-encoded `/` and `/`. I am not quite certain why you are using `buildChildDocumentsUriUsingTree()` in your current code. AFAIK, your `createFolderInFolder()` should simply be: `return DocumentFile.fromTreeUri(activity, Uri.parse(parentFolderUriString)).createDirectory(folderName);`. – CommonsWare Sep 28 '19 at 16:28
  • @CommonsWare The url is made of two substrings, the latter is appended by the code (so the slash character is used), is it to avoid? – P5music Sep 30 '19 at 08:11
  • If you mean that you appended the string yourself... you cannot make your own document `Uri` values. You need to get them from the provider. – CommonsWare Sep 30 '19 at 10:46
  • @CommonsWare That is the uri that the creation of folder made, so it is just a string reconstruction, but it should be legit. – P5music Sep 30 '19 at 10:51

1 Answers1

2

Following code when called with a full path to a local file and the contentscheme obtained by ACTION_OPEN_DOCUMENT_TREE copies the file to that saf location under the same or a different name that is up to you.

public static boolean copyFileToSafFolder(Context context, String filePath, String rootPath, String destFileName) {
    Uri uri = Uri.parse(rootPath);
    String docId = DocumentsContract.getTreeDocumentId(uri);
    Uri dirUri = DocumentsContract.buildDocumentUriUsingTree(uri, docId);
    Uri destUri = null;

    try {
        destUri = DocumentsContract.createDocument(context.getContentResolver(), dirUri, "*/*", destFileName);
    } catch (FileNotFoundException e) {
        e.printStackTrace();

        return false;
    }

    InputStream is = null;
    OutputStream os = null;
    try {
        is = new FileInputStream(filePath);
        os = context.getContentResolver().openOutputStream(destUri, "w");
        byte[] buffer = new byte[1024];

        int length;
        while ((length = is.read(buffer)) > 0)
            os.write(buffer, 0, length);

        is.close();
        os.flush();
        os.close();

        return true;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }

    return false;
}
andras
  • 3,305
  • 5
  • 30
  • 45
blackapps
  • 8,011
  • 2
  • 11
  • 25
  • Has the app to archive the root Uri (from the user selection) and consider the nested tree as a child? Aren't uris a general "address" to be used? – P5music Sep 30 '19 at 08:36
  • Dont understand what you ask. Did you try my code? I told you exactly when my code works. – blackapps Sep 30 '19 at 09:45
  • I have to change the uri archiving method in my app if I have to store the authorized root and then have also the remaining part of a uri. That's why I am asking about this being mandatory. – P5music Sep 30 '19 at 09:49
  • You should save data.getData().toString() in onActivityResult from ACTION_OPEN_DOCUMENT_TREE. Then use that saved string in the call to the copy function. Thats all. I wonder why you would save other things then data.getData().toString(). I also wonder what you mean with a remaining part of an uri. You can simply test the copy function directly in onActivityResult(). – blackapps Sep 30 '19 at 11:06
  • It works if I replace "String docId = DocumentsContract.getTreeDocumentId(uri);" with "String docId = DocumentsContract.getDocumentId(uri);" See also https://stackoverflow.com/questions/58186014/saf-file-written-in-parent-folder-not-in-the-right-path/58198099#58198099 – P5music Oct 02 '19 at 08:46
  • What works? My code works for the situation i described. What you are doing is unclear. Further you should not have copied my code to a new thread. You were supposed to test this function here. And why not ending the folder exist tread first? Stay at one subject until it is done. – blackapps Oct 02 '19 at 08:48
  • Your code is not working in the general case, indeed my question is not only about the ACTION_OPEN_DOCUMENT_TREE scenario. Furthermore my question is about a specific error in a specific scenario. – P5music Oct 02 '19 at 09:04
  • Sadly you did not tell in which specific scenario.Nor did you post reproducable code. So we do not know what you are doing. But even then you should not have started a new thread. – blackapps Oct 02 '19 at 09:09
  • Scenario is -generic SAF uris scenario. Threads can be started, do not be worried. – P5music Oct 02 '19 at 09:21