Cata
Cata

Reputation: 11211

Android DownloadManager and FileProvider

I want to use Android's DownloadManager to download a pdf and then enable the user to open it using his pdf viewer app.

To do that I am saving initiating the download using the following:

public String downloadFile(String url, String name) {
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setDescription("Downloading file: " + name);
        request.setTitle("My app name");
        request.allowScanningByMediaScanner();
     request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
        request.setMimeType("application/pdf");
        Uri destinationUri = Uri.fromFile(new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), name+".pdf"));
        request.setDestinationUri(destinationUri);
        manager.enqueue(request);
        return destinationUri.toString();
    }

I am saving the Uri and use it to open the PDF using a PDF view app like this:

public void openDownloadedFile(String uri) {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    File file = new File(uri);
    Uri uriForFile = FileProvider.getUriForFile(context.getApplicationContext(), context.getString(R.string.my_file_authority), file);
    intent.setDataAndType(uriForFile, "application/pdf");
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    context.startActivity(intent);
}

My application's Manifest file contains the following provider:

        <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="@string/my_file_authority"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>

And I also have the following content in the provider_paths:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path
        name="my_pdfs"
        path="."/>
    <external-path
        name="my_pdfs"
        path="."/>
    <files-path
        name="my_pdfs"
        path="."/>
</paths>

I know that there are 2 extra elements there but I just wanted to make sure I don't miss something out.

Unfortunately, this crashes with the following reason:

java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/Android/data/{my_app_id}/files/Download/filename_of_pdf.pdf
    at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(FileProvider.java:738)
    at android.support.v4.content.FileProvider.getUriForFile(FileProvider.java:417)

Some additional details:

minSdkVersion 21
targetSdkVersion 27

I am testing on a Samsung S8 and Android emulator with Android O.

Can someone please give me a clue about why is this happening? How can I know that Android is considering my provider_paths?

Thank you!

PS:

Upvotes: 2

Views: 7198

Answers (2)

atschpe
atschpe

Reputation: 121

I'm a bit late to the party here, but seeing I was struggling with this as well, I thought I'd provide the solution I finally found: After a lot of digging around, I found that we have to be careful with file:// and content://. Both are used as Uris but the first accesses the file publically (DownloadManager approach) and the other through permissions (FileProvider approach).

I'll sum up how I ended up doing it. In my case, I use the downloaded file within the app (extracting metadata from the mp3 and later playing it). I've included what worked for retrieving the file for my use, as here too, it was a lot of trial error to figure out which method to use to access the file. Hopefully, I can thus help others who are also trying to navigate this maze of Uris and permissions.

Downloading the file using DownloadManager

 //set up the download manager and the file destination
 final DownloadManager dlManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
 String destination = Environment.getExternalStorageDirectory() + "*APPFOLDER*";

 //ensure the folder exists before continuing
 File file = new File(destination);
 if (!file.exists())
 file.mkdirs();

 destination += "/" + "*FILENAME*";
 final Uri destinationUri = Uri.parse("file://" + destination);

 //create the download request
 DownloadManager.Request dlRequest = new DownloadManager.Request(uri);
 dlRequest.setDestinationUri(destinationUri);
 final long dlId = dlManager.enqueue(dlRequest);

Accessing the file

File myPath = new File(Environment.getExternalStorageDirectory(), "*APPFOLDER*");
File myFile = new File(myPath, "*FILENAME*");
Uri myUri = FileProvider.getUriForFile(ctxt, ctxt.getApplicationContext().getPackageName() + ".file_provider", myFile);
ctxt.grantUriPermission(ctxt.getApplicationContext().getPackageName(), myUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

    MediaMetadataRetriever mmr = new MediaMetadataRetriever();
    mmr.setDataSource(ctxt, myUri);

Upvotes: 2

CommonsWare
CommonsWare

Reputation: 1007554

File file = new File(uri);

A Uri is not a file.

java.lang.IllegalArgumentException: Failed to find configured root that contains /file:/storage/emulated/0/Android/data/{my_app_id}/files/Download/filename_of_pdf.pdf

/file:/storage/emulated/0/Android/data/{my_app_id}/files/Download/filename_of_pdf.pdf is not a filesystem path on Android (nor, AFAIK, for any other operating system).

To fix this:

  1. Hold onto the result of new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), name+".pdf") from your destinationUri instantiation as a File

  2. Use that File with getUriForFile()

Upvotes: 1

Related Questions