Sunday, 20 September 2020

Save a PDF file generated from a WebView after picking the file with Intent.ACTION_CREATE_DOCUMENT

Context: Android 10, API 29.

I print a PDF file generated from a WebView, but now I'd like to save it to a file. So I tried the Intent.ACTION_CREATE_DOCUMENT to pick the file and save it via the printAdapter's onWrite method.

The problem is that the file is always empty - 0 bytes - and no errors are raised. It justs calls onWriteFailed, but with an empty error message.

choosenFileUri has a value like content://com.android.providers.downloads.documents/document/37


The method I use to start the intent to pick a new file. Note that the result of this activity is a Uri:

fun startIntentToCreatePdfFile(fragment: Fragment, filename : String) {

    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, filename)
    }

    fragment.startActivityForResult(intent, IntentCreatePdfDocument)
}

The method I use to "print" the PDF to a file. The fileUri comes from the Intent.ACTION_CREATE_DOCUMENT:

fun printPdfToFile(
    context: Context,
    webView: WebView,
    fileUri: Uri
) {

    (context.getSystemService(Context.PRINT_SERVICE) as? PrintManager)?.let {
        val jobName = "Print PDF to save it"
        val printAdapter = webView.createPrintDocumentAdapter(jobName)

        val printAttributes = PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
            .setResolution(PrintAttributes.Resolution("pdf", "pdf", 600, 600))
            .setMinMargins(PrintAttributes.Margins.NO_MARGINS).build()

        printAdapter.onLayout(null, printAttributes, null, object : LayoutResultCallback() {
            override fun onLayoutFinished(info: PrintDocumentInfo, changed: Boolean) {

                context.contentResolver.openFileDescriptor(fileUri, "w")?.use {
                    printAdapter.onWrite(
                        arrayOf(PageRange.ALL_PAGES),
                        it,
                        CancellationSignal(),
                        object : WriteResultCallback() {

                        })
                }

            }
        }, null)
    }
}

What I do pick file onActivityResult:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (resultCode != Activity.RESULT_OK) {
        return null
    }

    if (requestCode != IntentCreatePdfDocument) {
        throw Exception("RequestCode not implemented: $requestCode")
    }

    val choosenFileUri = data?.data

    // If it is null, nothing to do
    if (choosenFileUri == null) {
        return
    }

    try {

        HtmlHelpers.savePdfFromHtml(
            requireContext(),
            "html document to be represented in the WebView",
            choosenFileUri)

    } catch (exception: Exception) {
        _logger.error(exception)
        Helpers.showError(requireActivity(), getString(R.string.generic_error))
    }

    dismiss()
}

...where HtmlHelpers.savePdfFromHtml is:

fun savePdfFromHtml(
    context: Context,
    htmlContent: String,
    fileUri: Uri
) {
    generatePdfFromHtml(
        context,
        htmlContent
    ) { webView ->

        PrintHelpers.printPdfToFile(
            context,
            webView,
            fileUri)
    }
}

...and generatePdfFromHtml is:

private fun generatePdfFromHtml(
    context: Context,
    htmlContent: String,
    onPdfCreated: (webView: WebView) -> Unit
) {

    val webView = WebView(context)
    webView.settings.javaScriptEnabled = true
    webView.webViewClient = object : WebViewClient() {

        override fun onPageFinished(webView: WebView, url: String) {
            onPdfCreated(webView)
        }

    }

    webView.loadDataWithBaseURL(
        null,
        htmlContent,
        "text/html; charset=utf-8",
        "UTF-8",
        null);

}

I checked all the other answer about this topic, but everyone creates manually the ParcelFileDescriptor instead of it in the onWrite method. Everyone does something like this:

fun getOutputFile(path: File, fileName: String): ParcelFileDescriptor? {
    val file = File(path, fileName)
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
}

But I cannot do this since I have only the Uri.


Edit: as suggested by @blackapps, I tried to open the output stream after I got the FileDescriptor, but I still got the same result:

context.contentResolver.openFileDescriptor(fileUri, "w")?.use {

    val fileDescriptor = it
    FileOutputStream(it.fileDescriptor).use {
        printAdapter.onWrite(
            arrayOf(PageRange.ALL_PAGES),
            fileDescriptor,
            CancellationSignal(),
            object : WriteResultCallback() {

            })

    }


}


from Save a PDF file generated from a WebView after picking the file with Intent.ACTION_CREATE_DOCUMENT

No comments:

Post a Comment