summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoViewPrintDocumentAdapter.java
diff options
context:
space:
mode:
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.java233
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);
+ }
+ }
+}