y390
y390

Reputation: 161

How to retrieve and open PDF file saved to downloads through MediaStore API in Android?

I am downloading a PDF file from a server and passing the response body bytestream into the function below, which is storing the PDF file successfully in the user downloads folder.

@RequiresApi(Build.VERSION_CODES.Q)
fun saveDownload(pdfInputStream: InputStream) {
    val values = ContentValues().apply {
        put(MediaStore.Downloads.DISPLAY_NAME, "test")
        put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
        put(MediaStore.Downloads.IS_PENDING, 1)
    }

    val resolver = context.contentResolver
    val collection = MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
    val itemUri = resolver.insert(collection, values)
    if (itemUri != null) {
        resolver.openFileDescriptor(itemUri, "w").use { parcelFileDescriptor ->
            ParcelFileDescriptor.AutoCloseOutputStream(parcelFileDescriptor)
                .write(pdfInputStream.readBytes())
        }
        values.clear()
        values.put(MediaStore.Downloads.IS_PENDING, 0)
        resolver.update(itemUri, values, null, null)
    }
}

Now once this function returns I want to open the saved PDF file. I've tried several ways to get this to work but the pickers always say that there is nothing to open the file. I think that there is either still a permissions issue going on (maybe I'm using the FileProvider wrong?), or perhaps the path is wrong, or it could be something else entirely.

Here's a couple of examples of what I've tried:

fun uriFromFile(context: Context, file: File): Uri {
    return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file)
}

a)

val openIntent = Intent(Intent.ACTION_VIEW)
openIntent.putExtra(Intent.EXTRA_STREAM, uriFromFile(this, File(this.getExternalFilesDir(DIRECTORY_DOWNLOADS)?.absolutePath.toString(), "test")))
openIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
openIntent.type = "application/pdf"
startActivity(Intent.createChooser(openIntent, "share.."))

b)

val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.putExtra(Intent.EXTRA_STREAM,  uriFromFile(this, File(this.getExternalFilesDir(null)?.absolutePath.toString(), "test.pdf")))
shareIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
shareIntent.type = "application/pdf"
startActivity(Intent.createChooser(shareIntent, "share.."))

c)

val file = File(itemUri.toString()) //itemUri from the saveDownload function
val target = Intent(Intent.ACTION_VIEW)
val newFile = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".provider", file);
target.setDataAndType(newFile, "application/pdf")
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Open File")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
ContextCompat.startActivity(this, intent, null)

d)

val target = Intent(Intent.ACTION_VIEW)
target.setDataAndType(Uri.parse("content://media/external_primary/downloads/2802"), "application/pdf"
target.flags = Intent.FLAG_ACTIVITY_NO_HISTORY
val intent = Intent.createChooser(target, "Open File")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
ContextCompat.startActivity(this, intent, null)

(also tried /test.pdf on the end of this URI, and replacing media with my authority name)

I have also added this to my manifest file within the application tags:

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

@xml/provider_paths is as follows, although I have tried various combinations in addition to this including the paths as ".":

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path name="files_root" path="/"/>
    <files-path name="files_root" path="/"/>
    <external-path name="files_root" path="/"/>
</paths>

As a side note, there is definitely pickers available capable of opening PDFs, and going into the file explorer and opening it from there works fine. When attempting to share instead of opening the sharing also fails.

Upvotes: 5

Views: 2866

Answers (3)

Abhishek Ippakayal
Abhishek Ippakayal

Reputation: 106

Here is the code that i use to open doc file with Uri.

fun viewPDFIntent(fileUri: Uri?, context: Context, title: String?, type: String) {
        val viewPDFIntent = Intent(Intent.ACTION_VIEW).apply {
            setDataAndType(fileUri, type)
            flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
        }
        context.startActivity(Intent.createChooser(viewPDFIntent, title))
    }

Here type for pdf is "application/pdf". You are getting created pdf uri in itemUri variable, pass this to first argument of this function.

Upvotes: 1

A Farmanbar
A Farmanbar

Reputation: 4788

According to Android Developer, MediaStore isn't being used for accessing non-media files such as pdf files:

If your app works with documents and files that don't exclusively contain media content, such as files that use the EPUB or PDF file extension, use the ACTION_OPEN_DOCUMENT intent action, as described in the guide on how to store and access documents and other files.

Moreover, there isn't any official solution to access non-media files by means of using Cursor and Content Provider. However, there is an official and clean code approach which I've tested it on Android 11 and worked as expected. here is:

public class retrieve_pdf_file {
    @RequiresApi(Build.VERSION_CODES.Q)
    public static void get(Activity activity) {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/pdf");
        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, MediaStore.Downloads.EXTERNAL_CONTENT_URI);
        activity.startActivityForResult(intent, main_activity.PICK_PDF_FILE);
    }

    public static void get(Activity activity, String filename) { // filename is used for lower that API level 29
        // older that  API level 29 approaches
        File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        // TODO
    }
}

And also, to get the selected pdf file's Uri you must listen for the activity's result:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (requestCode == PICK_PDF_FILE && resultCode == Activity.RESULT_OK) {
        System.out.println("request code: PICK_PDF_FILE && result code: OK");
        // The result data contains a URI for the document or directory that
        // the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            // Perform operations on the document using its URI.
            System.out.println(uri);
        } else {
            System.out.println("resultData is null");
        }
    } else {
        System.out.println("result code: NOT OK");
    }
}

This is the official solution that can be found in Android Developer for API level 29 or higher.

Upvotes: 2

Avinash Kumar Singh
Avinash Kumar Singh

Reputation: 763

Follow this step and code, it will manage everything from downloading your pdf and opening it.

Create a class name as DownloadTask and put the complete code given below

public class DownloadTask {

    private static final String TAG = "Download Task";
    private Context context;

    private String downloadFileUrl = "", downloadFileName = "";
    private ProgressDialog progressDialog;
    long downloadID;

    private BroadcastReceiver onDownloadComplete = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //Fetching the download id received with the broadcast
            long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            //Checking if the received broadcast is for our enqueued download by matching download id
            if (downloadID == id) {
                downloadCompleted(downloadID);
            }
        }
    };

    public DownloadTask(Context context, String downloadUrl) {
        this.context = context;

        this.downloadFileUrl = downloadUrl;


        downloadFileName = downloadFileUrl.substring(downloadFileUrl.lastIndexOf('/') + 1);//Create file name by picking download file name from URL
        Log.e(TAG, downloadFileName);

        context.registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
        downloadFile(downloadFileUrl);

    }

    public void downloadFile(String url) {

        try {
            File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), downloadFileName);

            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url))
                    .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)// Visibility of the download Notification
                    .setDestinationInExternalPublicDir(
                            Environment.DIRECTORY_DOWNLOADS,
                            downloadFileName
                    )
                    .setDestinationUri(Uri.fromFile(file))
                    .setTitle(downloadFileName)// Title of the Download Notification
                    .setDescription("Downloading")// Description of the Download Notification
                    .setAllowedOverMetered(true)// Set if download is allowed on Mobile network
                    .setAllowedOverRoaming(true);// Set if download is allowed on roaming network


            request.allowScanningByMediaScanner();
            DownloadManager downloadManager = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
            downloadID = downloadManager.enqueue(request);// enqueue puts the download request in the queue.

            progressDialog = new ProgressDialog(context);
            progressDialog.setMessage("Downloading...");
            progressDialog.setCancelable(false);
            progressDialog.show();
        } catch (Exception e) {
            Log.d("Download", e.toString());
        }


    }

    void downloadCompleted(long downloadID) {

        progressDialog.dismiss();

        new AlertDialog.Builder(context)
                .setTitle("Document")
                .setMessage("Document Downloaded Successfully")

                .setPositiveButton("Open", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {

                        openDownloadedAttachment(downloadID);
                    }
                })

                // A null listener allows the button to dismiss the dialog and take no further action.
                .setNegativeButton(android.R.string.no, null)
                .setIcon(android.R.drawable.ic_dialog_alert)
                .show();

        context.unregisterReceiver(onDownloadComplete);

    }

    Uri path;

    private void openDownloadedAttachment(final long downloadId) {
        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        DownloadManager.Query query = new DownloadManager.Query();
        query.setFilterById(downloadId);
        Cursor cursor = downloadManager.query(query);
        if (cursor.moveToFirst()) {
            int downloadStatus = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
            String downloadLocalUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
            String downloadMimeType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE));
            if ((downloadStatus == DownloadManager.STATUS_SUCCESSFUL) && downloadLocalUri != null) {
                path = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".provider", new File(Uri.parse(downloadLocalUri).getPath()));
                //path = Uri.parse(downloadLocalUri);
                Intent pdfIntent = new Intent(Intent.ACTION_VIEW);

                pdfIntent.setDataAndType(path, downloadMimeType);

                pdfIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                try {
                    context.startActivity(pdfIntent);
                } catch (ActivityNotFoundException e) {
                    Toast.makeText(context, "No Application available to view PDF", Toast.LENGTH_SHORT).show();
                }
            }
        }
        cursor.close();
    }
}

And then download your pdf like this from your activity.

new DownloadTask(this, "PDF_URL");

And from your fragment

new DownloadTask(getContext(), "PDF_URL");

After download completed it will open your pdf automatically.

Upvotes: 2

Related Questions