diff options
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java')
-rw-r--r-- | mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java new file mode 100644 index 0000000000..806343a637 --- /dev/null +++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java @@ -0,0 +1,233 @@ +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- + * vim: ts=4 sw=4 expandtab: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +package org.mozilla.geckoview; + +import android.content.Context; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintDocumentInfo; +import android.util.Log; +import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import org.mozilla.gecko.util.ThreadUtils; + +public class GeckoViewPrintDocumentAdapter extends PrintDocumentAdapter { + private static final String LOGTAG = "GVPrintDocumentAdapter"; + private static final String PRINT_NAME_DEFAULT = "Document"; + private String mPrintName = PRINT_NAME_DEFAULT; + private File mPdfFile; + private GeckoResult<File> mGeneratedPdfFile; + private Boolean mDoDeleteTmpPdf; + private GeckoResult<Boolean> mPrintDialogFinish = null; + + /** + * Default GeckoView PrintDocumentAdapter to be used with a PrintManager to print documents using + * the default Android print functionality. Will make a temporary PDF file from InputStream. + * + * @param pdfInputStream an input stream containing a PDF + * @param context context that should be used for making a temporary file + */ + public GeckoViewPrintDocumentAdapter( + @NonNull final InputStream pdfInputStream, @NonNull final Context context) { + this.mDoDeleteTmpPdf = true; + this.mGeneratedPdfFile = pdfInputStreamToFile(pdfInputStream, context); + } + + /** + * GeckoView PrintDocumentAdapter to be used with a PrintManager to print documents using the + * default Android print functionality. Will make a temporary PDF file from InputStream. + * + * @param pdfInputStream an input stream containing a PDF + * @param context context that should be used for making a temporary file + * @param printDialogFinish result to report that the print finished + */ + public GeckoViewPrintDocumentAdapter( + @NonNull final InputStream pdfInputStream, + @NonNull final Context context, + @Nullable final GeckoResult<Boolean> printDialogFinish) { + this.mDoDeleteTmpPdf = true; + this.mGeneratedPdfFile = pdfInputStreamToFile(pdfInputStream, context); + this.mPrintDialogFinish = printDialogFinish; + } + + /** + * Default GeckoView PrintDocumentAdapter to be used with a PrintManager to print documents using + * the default Android print functionality. Will use existing PDF file for rendering. The filename + * may be displayed to users. + * + * <p>Note: Recommend using other constructor if the PDF file still needs to be created so that + * the UI reflects progress. + * + * @param pdfFile PDF file + */ + public GeckoViewPrintDocumentAdapter(@NonNull final File pdfFile) { + this.mPdfFile = pdfFile; + this.mDoDeleteTmpPdf = false; + this.mPrintName = mPdfFile.getName(); + } + + /** + * Writes the PDF InputStream to a file for the PrintDocumentAdapter to use. + * + * @param pdfInputStream - InputStream containing a PDF + * @param context context that should be used for making a temporary file + * @return temporary PDF file + */ + @AnyThread + public static @Nullable File makeTempPdfFile( + @NonNull final InputStream pdfInputStream, @NonNull final Context context) { + File file = null; + try { + file = File.createTempFile("temp", ".pdf", context.getCacheDir()); + } catch (final IOException ioe) { + Log.e(LOGTAG, "Could not make a file in the cache dir: ", ioe); + } + final int bufferSize = 8192; + final byte[] buffer = new byte[bufferSize]; + try (final OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { + int len; + while ((len = pdfInputStream.read(buffer)) != -1) { + out.write(buffer, 0, len); + } + } catch (final IOException ioe) { + Log.e(LOGTAG, "Writing temporary PDF file failed: ", ioe); + } + return file; + } + + /** + * Utility to make a PDF file from the input stream in the background. + * + * @param pdfInputStream - InputStream containing a PDF + * @param context context that should be used for making a temporary file + * @return gecko result with the file + */ + private @NonNull GeckoResult<File> pdfInputStreamToFile( + final @NonNull InputStream pdfInputStream, final @NonNull Context context) { + final GeckoResult<File> result = new GeckoResult<>(); + ThreadUtils.postToBackgroundThread( + () -> { + result.complete(makeTempPdfFile(pdfInputStream, context)); + }); + return result; + } + + @Override + public void onLayout( + final PrintAttributes oldAttributes, + final PrintAttributes newAttributes, + final CancellationSignal cancellationSignal, + final LayoutResultCallback layoutResultCallback, + final Bundle bundle) { + if (cancellationSignal.isCanceled()) { + layoutResultCallback.onLayoutCancelled(); + return; + } + final PrintDocumentInfo pdi = + new PrintDocumentInfo.Builder(mPrintName) + .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT) + .build(); + layoutResultCallback.onLayoutFinished(pdi, true); + } + + /** + * Handles onWrite functionality. Recommend running on a background thread as onWrite is on the + * main thread. + * + * @param pdfFile - PDF file to generate print preview with. + * @param parcelFileDescriptor - onWrite parcelFileDescriptor + * @param writeResultCallback - onWrite writeResultCallback + */ + private void onWritePdf( + final @Nullable File pdfFile, + final @NonNull ParcelFileDescriptor parcelFileDescriptor, + final @NonNull WriteResultCallback writeResultCallback) { + InputStream input = null; + OutputStream output = null; + try { + input = new FileInputStream(pdfFile); + output = new FileOutputStream(parcelFileDescriptor.getFileDescriptor()); + final int bufferSize = 8192; + final byte[] buffer = new byte[bufferSize]; + int bytesRead; + while ((bytesRead = input.read(buffer)) > 0) { + output.write(buffer, 0, bytesRead); + } + writeResultCallback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES}); + } catch (final Exception ex) { + Log.e(LOGTAG, "Could not complete onWrite for printing: ", ex); + writeResultCallback.onWriteFailed(null); + } finally { + try { + input.close(); + output.close(); + } catch (final Exception ex) { + Log.e(LOGTAG, "Could not close i/o stream: ", ex); + } + } + } + + @Override + public void onWrite( + final PageRange[] pageRanges, + final ParcelFileDescriptor parcelFileDescriptor, + final CancellationSignal cancellationSignal, + final WriteResultCallback writeResultCallback) { + + ThreadUtils.postToBackgroundThread( + () -> { + if (mGeneratedPdfFile != null) { + mGeneratedPdfFile.then( + file -> { + if (mPrintName == PRINT_NAME_DEFAULT) { + mPrintName = file.getName(); + } + onWritePdf(file, parcelFileDescriptor, writeResultCallback); + return null; + }); + } else { + onWritePdf(mPdfFile, parcelFileDescriptor, writeResultCallback); + } + }); + } + + @Override + public void onFinish() { + // Remove the temporary file when the printing system is finished. + try { + if (mDoDeleteTmpPdf) { + if (mPdfFile != null) { + mPdfFile.delete(); + } + if (mGeneratedPdfFile != null) { + mGeneratedPdfFile.then( + file -> { + file.delete(); + return null; + }); + } + } + } catch (final NullPointerException npe) { + // Silence the exception. We only want to delete a real file. We don't + // care if the file doesn't exist. + } + if (this.mPrintDialogFinish != null) { + mPrintDialogFinish.complete(true); + } + } +} |