Home > OS >  Open PDF after download with DownloadManager and FileProvider on Android Q (10)
Open PDF after download with DownloadManager and FileProvider on Android Q (10)

Time:03-18

targetSdkVersion: 30

In our App we have a feature, where we download files (mostly pdf) to the public download folder and start an intent afterwards to open it. Our code works fine for android apps with api >= 28 and >= 30. Just our app on Android 10 (sdkVersion 29) will try to open the document and instantly closes the activity that tried to display the pdf. The logcat shows following error:

22-03-17 14:23:42.486 12161-15168/? E/DisplayData: openFd: java.io.FileNotFoundException: open failed: EACCES (Permission denied)
22-03-17 14:23:42.486 12161-15168/? E/PdfLoader: Can't load file (doesn't open)  Display Data [PDF : download.pdf]  ContentOpenable, uri: content://com.example.fileprovider/Download/download.pdf

If I grant the files permission in the app settings, it will work flawlessly but if I understood the android documentation correctly this should not be necessary since Android 10. Especially because there are no problems on Android 11 and Android 12 devices that do not have this permission. On all Android versions the file will be downloaded correctly and the user could manually open it from the download section of his device.

This is the Android Manifest section for the fileprovider

<provider
    android:name="androidx.core.content.FileProvider"        
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
       android:name="android.support.FILE_PROVIDER_PATHS"
       android:resource="@xml/filepaths" />
</provider>

The filepaths XML file

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="external_files"
        path="." />
    <external-path
        name="Download"
        path="Download/"/>
    <files-path
        name="files"
        path="." />
    <external-cache-path
        name="external_cache"
        path="." />
</paths>

This is the code with the DownloadManager usage to download the files

public static long downloadFile(Context context, String url, String fileName) {
    DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);

    try {
        DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
        request.setTitle(fileName)
                .setDescription(Localization.getStringClient("file_download_progress"))
                .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);

        return downloadManager.enqueue(request);
    } catch (Exception e) {
        e.printStackTrace();
        Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
    }
    return -1;
}

The broadcast receiver that listens on the download progress

public class DownloadBroadcastReceiver extends BroadcastReceiver {

    private long downloadId = -2;

    public void setDownloadId(long downloadId) {
        this.downloadId = downloadId;
    }

    @Override
    public void onReceive(Context context, Intent intent) {

        long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        if (id == downloadId) {
            DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
            DownloadManager.Query query = new DownloadManager.Query();
            query.setFilterById(id);
            Cursor c = downloadManager.query(query);
            if (c != null) {
                c.moveToFirst();
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
                if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
                    String uriString = c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
                    String mediaType = c.getString(c.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
                    Uri fileUri = Uri.parse(uriString);

                    FileUtils.openFile(context, fileUri, mediaType);
                } else if (DownloadManager.STATUS_FAILED == c.getInt(columnIndex)) {
                    Toast.makeText(context, Localization.getStringClient("error_downloading_asset"), Toast.LENGTH_SHORT).show();
                }
                c.close();
            }
        }
    }
}

The code to open the file. Only on Android 10 the app closes the activity instantly again.

public static void openFile(Context context, Uri fileUri, String mediaType) {
    File file = new File(fileUri.getPath());
    Uri uri = FileProvider.getUriForFile(
            context,
            context.getApplicationContext().getPackageName()   ".fileprovider",
            file
    );

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(uri, mediaType);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    try {
        context.startActivity(intent);
    } catch (ActivityNotFoundException e) {
        e.printStackTrace();
        Toast.makeText(context, Localization.getStringClient("open_file_no_activity"), Toast.LENGTH_SHORT).show();
    }
}

And a download call in the looks like this

@Override
public void startPDFDownload(String pdfDownloadUrl, String fileName) {
    long downloadId = FileUtils.downloadFile(requireContext(), pdfDownloadUrl, fileName);

    if (downloadId > -1) {
        DownloadBroadcastReceiver receiver = new DownloadBroadcastReceiver();
        receiver.setDownloadId(downloadId);
        requireContext().registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }
}

I think I have a false understanding of how file handling works in Android 10 but I do not know where I have to adjust the code or configuration. Help is really appreciated. Currently as a workaround we ask for the permission for WRITE_EXTERNAL_STORAGE to open downloaded files on Android 10. But I would prefer to do it the right way.

CodePudding user response:

You should not use FileProvider to obtain an uri for your file.

You can get an uri from DownloadManager and use it to serve your file.

Code would be the same for all Android versions.

Not a single permission needed.

  • Related