PVoLan
PVoLan

Reputation: 1040

Android PrintManager: process gets killed, print outputs file of zero bytes length

Here is a sample code. It is simple, it takes a PDF file from assets and sends it to a standard printer framework. It is based on this Google guide with minimum modifications (I do not process paging properly, but that doesn't matter).

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        findViewById<Button>(R.id.button).setOnClickListener{ Printer().print(this) }
        //...
    }
    //...
}


class Printer {

    fun print(context : Context) {
        log("Printer.print")
        val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager

        val attrib = PrintAttributes.Builder()
            .setMediaSize(PrintAttributes.MediaSize.ISO_A4)
            .build()

        printManager.print("Test print job name", SampleDocumentAdapter(context, "sample.pdf"), attrib)
        log("Printer.print done")
    }


    private inner class SampleDocumentAdapter(
        private val context : Context,
        private val assetFileName: String
    ) : PrintDocumentAdapter(){

        override fun onStart() {
            log("SampleDocumentAdapter.onStart")
            super.onStart()
        }

        override fun onFinish() {
            log("SampleDocumentAdapter.onFinish")
            super.onFinish()
        }

        override fun onLayout(oldAttrs: PrintAttributes?, newAttrs: PrintAttributes,cancelSignal: CancellationSignal,
            callback: LayoutResultCallback, extra: Bundle?)
        {
            log("SampleDocumentAdapter.onLayout")
            if (cancelSignal.isCanceled) {
                callback.onLayoutCancelled()
            } else {
                PrintDocumentInfo.Builder("Test printer output")
                    .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
                    .build()
                    .also {
                        callback.onLayoutFinished(it, true)
                    }
            }
            log("SampleDocumentAdapter.onLayout done")
        }

        override fun onWrite(pages: Array<out PageRange>?, destination: ParcelFileDescriptor,
                             cancelSignal: CancellationSignal, callback: WriteResultCallback)
        {
            log("SampleDocumentAdapter.onWrite")
            CoroutineScope(Dispatchers.IO).launch {
                launch {
                    log("SampleDocumentAdapter.onWrite async")

                    try {
                        context.assets.open(assetFileName).use { inStream ->
                            val buffer = ByteArray(16384)

                            FileOutputStream(destination.fileDescriptor).use { outStream ->
                                var totalBytes = 0
                                while (true) {
                                    val length = inStream.read(buffer)
                                    if (length < 0) break
                                    outStream.write(buffer, 0, length)
                                    totalBytes += length
                                }
                                log("SampleDocumentAdapter.onWrite - total of $totalBytes bytes written")
                            }
                        }

                        destination.close()
                        callback.onWriteFinished(arrayOf(PageRange.ALL_PAGES))

                        log("SampleDocumentAdapter.onWrite done")
                    } catch (e: Exception){
                        log("SampleDocumentAdapter.onWrite error $e")
                        callback.onWriteFailed("SampleDocumentAdapter.onWrite error $e")
                    }
                }
            }

            log("SampleDocumentAdapter.onWrite done")
        }
    }
}

Here is full code. You can also download full repo if you want.

I use "Save as PDF" printer simulator for testing

The issue. While user goes through the printing procedure (printing options, choosing file to save, etc.) it gets switched to another app in another process (most probably the Google's File selector app is the reason). My app goes to the background. Sometimes, but not always, this leads my app to be killed by OS (since we're in the background, aren't we?).

If that happens, there is no proper handling from the PrintManager framework. Framework still uses our code (SampleDocumentAdapter part) as a callback to receive printing data, but when we are died, it just doesn't care. As a result, I receive a print output file (pdf) with 0 bytes length and no any error message. No SampleDocumentAdapter.onFinish is getting called (although SampleDocumentAdapter.onWrite typically works properly). The whole PrintManager framework seems to have no any instrument to handle app lifecycle properly, it just breaks.

There is too little of any print-related topics on stackoverflow, so I could not find any advice in the Internet. I was also unable to find any paragraph in documentation pointing to such a kind of issue. In my real project, I was able to workaround the issue using fake foreground service to prevent my process from being killed, but it looks quite a hacky solution for such a simple task

The question: is it true that Google made such a bug in such a simple case, or have I just missed something in documentation?

Upvotes: 0

Views: 32

Answers (0)

Related Questions