5

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.

y390
  • 121
  • 1
  • 5
  • no need to use bold, we can read it just fine without it :) – a_local_nobody Jan 20 '21 at 16:28
  • @a_local_nobody hah sorry, my bad :) – y390 Jan 20 '21 at 16:29
  • unfortunately i can't really help with your question, so the best i could do was the edit, but i'm sure you'll find an answer – a_local_nobody Jan 20 '21 at 16:29
  • Why would you pick? You have already an uri. This one: val itemUri = resolver.insert(collection...... – blackapps Jan 20 '21 at 16:35
  • 1
    @blackapps I tried using that Uri, but it doesn't work, see (c) – y390 Jan 20 '21 at 16:37
  • That was the wrong way. You start with an uri. Use it! Now you messed around with the File class and used a FileProvidet to create another -invalid- uri. Throw it all away. Use the uri you already have. – blackapps Jan 20 '21 at 16:49
  • @blackapps I have removed the file and newFile variables and put the URI directly into the target.setDataAndType. This still doesn't work, it's slightly closer in the fact that it does open up the new viewing activity, but the file is blank and is not the file intended. I think it is just creating this there and then. For reference the URI from the resolver.insert.. is "content://media/external_primary/downloads/2802". – y390 Jan 20 '21 at 20:03
  • Yes, that is a nice uri. Post your new code as d). – blackapps Jan 20 '21 at 21:41
  • @y390 Did you find a solution? I'm struggling with the exact same problem. – Andre Romano May 24 '21 at 09:13
  • @AndreRomano nope, never found a way and didn't find anyone else who did either. Determined it's not possible with the new scoped storage – y390 Jul 24 '21 at 10:58
  • @Muhammad Irfan: The question of the OP was about downloading and storing of a PDF file using the Mediastore API (and the later "view" uses the Mediastore API as well). Your question ist much broader than the original one: "Question is same how would you open a saved pdf file?" - would you accept an answer that uses a FileProvider insteadt of the MediaStore API? – Michael Fehr Aug 09 '21 at 22:21

3 Answers3

2

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.

2

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.

A Farmanbar
  • 4,381
  • 5
  • 24
  • 42
1

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.