1

My Android has to copy a file from private app folder to a SAF location that was previously granted permissions and authorized. The code is:

static boolean copyDocumentFileToTargetFolderWithNewName(Activity activity, String docUri,String targetFolderUri,String newName)
{
    deleteIfExisting(activity,Uri.parse(targetFolderUri),newName);
    ContentResolver resolver = activity.getContentResolver();
    boolean result=false;
    Log.d("copy",docUri+" "+targetFolderUri+" "+newName);
    Log.d("doc exists",String.valueOf(fileExists(activity,docUri)));  //error here
    try {
        DocumentsContract.copyDocument(resolver,Uri.parse(docUri),Uri.parse(targetFolderUri));//or error here
        DocumentsContract.renameDocument(resolver,Uri.parse(docUri),newName);
        result=true;
    } catch (FileNotFoundException e) {
        result=false;
    }

    return result;
}

and

static public boolean fileExists(Activity activity,String fileUriString)
{
    ContentResolver contentResolver;
    contentResolver = activity.getContentResolver();
    String parentFolderUriString=StringUtils.fromLastSlashLeft(fileUriString);
    String fileName=StringUtils.fromLastSlashRight(fileUriString);
    Uri parentFolderUri=Uri.parse(parentFolderUriString);
    boolean exists;
    DocumentFile docFile=DocumentFile.fromTreeUri(activity,parentFolderUri).findFile(fileName); //error here
    exists= (null!=docFile);
    return exists;
}

(with some self-explanatory string utility calls)

Here the result from the Log.d() line

D/copy: file:///storage/emulated/0/Android/data/com.myappname.app/folder/subfolder/file.txt content://com.android.providers.downloads.documents/tree/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2FSAFfolder/newname.txt

I get

java.lang.IllegalArgumentException: Invalid URI: file:///storage/emulated/0/Android/data/com.myappname.app/folder/subfolder/file.txt

at

DocumentFile docFile=DocumentFile.fromTreeUri(activity,parentFolderUri).findFile(fileName);

in the second method

or, when Log.d() is commented,

java.lang.NullPointerException: Attempt to invoke virtual method 'android.os.Bundle android.content.ContentProviderClient.call(java.lang.String, java.lang.String, android.os.Bundle)' on a null object reference

at this line:

DocumentsContract.copyDocument(resolver,Uri.parse(docUri),Uri.parse(targetFolderUri));

in the first method. What's wrong with my code? Aren't private folder uris authorized? How to authorize them?

P5music
  • 3,197
  • 2
  • 32
  • 81

1 Answers1

1

What's wrong with my code?

Neither docUri nor parentFolderUri is a document Uri. They are Uri values that you created using Uri.fromFile() or something. You can tell because they have the file scheme.

With regards to your immediate crash, fromTreeUri() requires a document Uri. Use fromFile() for file Uri values.

Once you get past that, I expect that you will crash in your copyDocument() call. That's what I originally thought the problem was, until I re-read your question. You are trying to copy a non-document Uri (docUri), and the JavaDocs for copyDocument() state that the first parameter needs to be a document Uri:

document with Document#FLAG_SUPPORTS_COPY This value must never be null.

When DocumentsContract refers to a Uri as a "document", it means a Uri obtained through DocumentsContract itself. That would include a Uri obtained through ACTION_OPEN_DOCUMENT or a Uri obtained through a tree DocumentFile. However, that will not cover:

  • A Uri created by Uri.fromFile()
  • A Uri from FileProvider.getUriForFile()
  • A Uri created by Uri.parse() from an http or https URL
  • Etc.

Aren't private folder uris authorized?

It is not a question of "authorization". fromTreeUri() expects a document Uri. copyDocument() expects a document Uri. You cannot use them with non-document Uri values, that's all.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • So, Do I have to send an Intent or there is a way to convert file:/// uris to suitably authorized content:/// form? – P5music Sep 28 '19 at 13:22
  • @P5music: I don't know of any way to accomplish that. Note that you can copy your file to a document using ordinary Java file I/O, using `ContentResolver` and its `openOutputStream()` method to get an `OutputStream` to write the bytes to. AFAIK `copyDocument()` is there in case both documents are handled by the same provider and there happen to be "shortcuts" that don't involve an actual local copy operation. For example, copying from one Google Drive document to another probably just involves a network call to the Google server, and the actual copy is made there. – CommonsWare Sep 28 '19 at 14:04
  • Yes, I think there are multiple ways. Maybe also passing an Intent. I have to check. I can also create my own copy method using OutpuStream. – P5music Sep 28 '19 at 14:17