chromium/android_webview/java/src/org/chromium/android_webview/AwPdfExporter.java

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.android_webview;

import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PrintAttributes;
import android.util.Log;
import android.view.ViewGroup;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.android_webview.common.Lifetime;

/**
 * Export the android webview as a PDF.
 *
 * Owned by Java-side AwContents. This object is lazy-instantiated when needed
 * and receives a pointer to the native counterpart, which is owned by the
 * native side of AwContents.
 */
@Lifetime.WebView
@JNINamespace("android_webview")
public class AwPdfExporter {
    private static final String TAG = "AwPdfExporter";
    private long mNativeAwPdfExporter;
    // TODO(sgurun) result callback should return an int/object indicating errors.
    // potential errors: invalid print parameters, already pending, IO error
    private AwPdfExporterCallback mResultCallback;
    private PrintAttributes mAttributes;
    private ParcelFileDescriptor mFd;
    // Maintain a reference to the top level object (i.e. WebView) since in a common
    // use case (offscreen webview) application may expect the framework's print manager
    // to own the Webview (via PrintDocumentAdapter).
    // NOTE: it looks unused, but please do not remove this reference. There is also a proguard
    // configuration to prevent this variable to be optimized away. Any name changes should
    // be reflected there.
    private ViewGroup mContainerView;

    /** AwPdfExporter callback used to call onWrite* callbacks in Android framework. */
    public interface AwPdfExporterCallback {
        /**
         * Called by the native side when PDF generation is done.
         * @param pageCount How many pages native side wrote to PDF file descriptor. Non-positive
         *                  value indicates native side writing failed.
         */
        public void pdfWritingDone(int pageCount);
    }

    AwPdfExporter(ViewGroup containerView) {
        setContainerView(containerView);
    }

    public void setContainerView(ViewGroup containerView) {
        mContainerView = containerView;
    }

    public void exportToPdf(
            final ParcelFileDescriptor fd,
            PrintAttributes attributes,
            int[] pages,
            AwPdfExporterCallback resultCallback,
            CancellationSignal cancellationSignal) {
        if (fd == null) {
            throw new IllegalArgumentException("fd cannot be null");
        }
        if (resultCallback == null) {
            throw new IllegalArgumentException("resultCallback cannot be null");
        }
        if (mResultCallback != null) {
            throw new IllegalStateException("printing is already pending");
        }
        if (attributes.getMediaSize() == null) {
            throw new IllegalArgumentException("attributes must specify a media size");
        }
        if (attributes.getResolution() == null) {
            throw new IllegalArgumentException("attributes must specify print resolution");
        }
        if (attributes.getMinMargins() == null) {
            throw new IllegalArgumentException("attributes must specify margins");
        }
        if (mNativeAwPdfExporter == 0) {
            resultCallback.pdfWritingDone(0);
            return;
        }
        mResultCallback = resultCallback;
        mAttributes = attributes;
        mFd = fd;
        AwPdfExporterJni.get()
                .exportToPdf(
                        mNativeAwPdfExporter,
                        AwPdfExporter.this,
                        mFd.getFd(),
                        pages,
                        cancellationSignal);
    }

    @CalledByNative
    private void setNativeAwPdfExporter(long nativePdfExporter) {
        mNativeAwPdfExporter = nativePdfExporter;
        // Handle the cornercase that the native side is destroyed (for example
        // via Webview.Destroy) before it has a chance to complete the pdf exporting.
        if (nativePdfExporter == 0 && mResultCallback != null) {
            try {
                mResultCallback.pdfWritingDone(0);
                mResultCallback = null;
            } catch (IllegalStateException ex) {
                // Swallow the illegal state exception here. It is possible that app
                // is going away and binder is already finalized. b/25462345
            }
        }
    }

    private static int getPrintDpi(PrintAttributes attributes) {
        // TODO(sgurun) android print attributes support horizontal and
        // vertical DPI. Chrome has only one DPI. Revisit this.
        int horizontalDpi = attributes.getResolution().getHorizontalDpi();
        int verticalDpi = attributes.getResolution().getVerticalDpi();
        if (horizontalDpi != verticalDpi) {
            Log.w(
                    TAG,
                    "Horizontal and vertical DPIs differ. Using horizontal DPI "
                            + " hDpi="
                            + horizontalDpi
                            + " vDPI="
                            + verticalDpi);
        }
        return horizontalDpi;
    }

    @CalledByNative
    private void didExportPdf(int pageCount) {
        mResultCallback.pdfWritingDone(pageCount);
        mResultCallback = null;
        mAttributes = null;
        // The caller should close the file.
        mFd = null;
    }

    @CalledByNative
    private int getPageWidth() {
        return mAttributes.getMediaSize().getWidthMils();
    }

    @CalledByNative
    private int getPageHeight() {
        return mAttributes.getMediaSize().getHeightMils();
    }

    @CalledByNative
    private int getDpi() {
        return getPrintDpi(mAttributes);
    }

    @CalledByNative
    private int getLeftMargin() {
        return mAttributes.getMinMargins().getLeftMils();
    }

    @CalledByNative
    private int getRightMargin() {
        return mAttributes.getMinMargins().getRightMils();
    }

    @CalledByNative
    private int getTopMargin() {
        return mAttributes.getMinMargins().getTopMils();
    }

    @CalledByNative
    private int getBottomMargin() {
        return mAttributes.getMinMargins().getBottomMils();
    }

    @NativeMethods
    interface Natives {
        void exportToPdf(
                long nativeAwPdfExporter,
                AwPdfExporter caller,
                int fd,
                int[] pages,
                CancellationSignal cancellationSignal);
    }
}