Search code examples
androidandroid-download-managerandroid-fileprovider

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:

  • I tried to use the external public directory for the DownloadManager but it crashes saying that it doesn't have permissions even though in my manifest I do have WRITE_EXTERNAL_STORAGE permission and I ask for permission to the user but the dialog doesn't appear.
  • I also replaced "." from the provider_paths.xml file with "Download" or "Android" or even "Android/data/{my_app_id}/files/Download/" but I got the same crash

Solution

  • 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()