summaryrefslogtreecommitdiffstats
path: root/printing
diff options
context:
space:
mode:
authorcimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-14 17:29:24 +0000
committercimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-11-14 17:29:24 +0000
commitb6f76a1ae51b531ef1daabd9bd05fd8c87e61cd4 (patch)
tree911fce788076849f876b6fcb2a297edecd053451 /printing
parentb50d4e7248ebd524b7a1e07b4cdf232ec54f0f1e (diff)
downloadchromium_src-b6f76a1ae51b531ef1daabd9bd05fd8c87e61cd4.zip
chromium_src-b6f76a1ae51b531ef1daabd9bd05fd8c87e61cd4.tar.gz
chromium_src-b6f76a1ae51b531ef1daabd9bd05fd8c87e61cd4.tar.bz2
Upstream Android printing code.
With regards to the original downstream code, there are some differences: * PrintintControllerComponent(Impl) are renamed to PrintingController(Impl) * ChromeComponentFactory is renamed into PrintingControllerFactory * Some obsolete documentation removed/updated. More refactoring and tests will follow. BUG=315229 TBR=vitalybuka@chromium.org Review URL: https://codereview.chromium.org/64093004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@235171 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'printing')
-rw-r--r--printing/DEPS1
-rw-r--r--printing/android/java/src/org/chromium/printing/Printable.java18
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingContext.java162
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingContextInterface.java24
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingController.java85
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingControllerFactory.java33
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java349
-rw-r--r--printing/printing.gyp35
-rw-r--r--printing/printing_context_android.cc246
9 files changed, 953 insertions, 0 deletions
diff --git a/printing/DEPS b/printing/DEPS
index 64123b8..bc43b41 100644
--- a/printing/DEPS
+++ b/printing/DEPS
@@ -1,4 +1,5 @@
include_rules = [
+ "+jni",
"+skia/ext",
"+third_party/icu/source/common/unicode",
"+third_party/icu/source/i18n/unicode",
diff --git a/printing/android/java/src/org/chromium/printing/Printable.java b/printing/android/java/src/org/chromium/printing/Printable.java
new file mode 100644
index 0000000..25c0ab2
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/Printable.java
@@ -0,0 +1,18 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+/**
+ * Describes a class that can initiate the printing process.
+ *
+ * This interface helps decoupling Tab from the printing implementation and helps with testing.
+ */
+public interface Printable {
+ /** Start the PDF generation process. */
+ boolean print();
+
+ /** Get the title of the generated PDF document. */
+ String getTitle();
+}
diff --git a/printing/android/java/src/org/chromium/printing/PrintingContext.java b/printing/android/java/src/org/chromium/printing/PrintingContext.java
new file mode 100644
index 0000000..e63c978
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/PrintingContext.java
@@ -0,0 +1,162 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+import org.chromium.base.CalledByNative;
+import org.chromium.base.JNINamespace;
+import org.chromium.base.ThreadUtils;
+
+import android.content.Context;
+
+import android.util.Log;
+import android.util.SparseArray;
+
+/**
+ * This class is responsible for communicating with its native counterpart through JNI to handle
+ * the generation of PDF. On the Java side, it works with a {@link PrintingController}
+ * to talk to the framework.
+ */
+@JNINamespace("printing")
+public class PrintingContext implements PrintingContextInterface {
+
+ private static final String TAG = "PrintingContext";
+
+ /** Whether the framework supports printing. */
+ public static final boolean sIsPrintingAvailable = isPrintingAvailable();
+
+ /**
+ * The full class name of the print manager used to test whether printing functionality is
+ * available.
+ */
+ private static final String PRINT_MANAGER_CLASS_NAME = "android.print.PrintManager";
+
+ /**
+ * Mapping from a file descriptor (as originally provided from
+ * {@link PrintDocumentAdapter#onWrite}) to a PrintingContext.
+ *
+ * This is static because a static method of the native code (inside PrintingContextAndroid)
+ * needs to find Java PrintingContext class corresponding to a file descriptor.
+ **/
+ private static final SparseArray<PrintingContext> sPrintingContextMap =
+ new SparseArray<PrintingContext>();
+
+ /** The controller this object interacts with, which in turn communicates with the framework. */
+ private final PrintingController mController;
+
+ /** The pointer to the native PrintingContextAndroid object. */
+ private final int mNativeObject;
+
+ private PrintingContext(Context context, int ptr) {
+ mController = PrintingControllerFactory.getPrintingController(context);
+ mNativeObject = ptr;
+ }
+
+ /**
+ * @return Whether printing is supported by the platform.
+ */
+ private static boolean isPrintingAvailable() {
+ // TODO(cimamoglu): Get rid of reflection once Build.VERSION_CODES.KEY_LIME_PIE is fixed.
+ try {
+ Class.forName(PRINT_MANAGER_CLASS_NAME);
+ } catch (ClassNotFoundException e) {
+ Log.d(TAG, "PrintManager not found on device");
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Updates sPrintingContextMap to map from the file descriptor to this object.
+ * @param fileDescriptor The file descriptor passed down from
+ * {@link PrintDocumentAdapter#onWrite}.
+ * @param delete If true, delete the entry (if it exists). If false, add it to the map.
+ */
+ @Override
+ public void updatePrintingContextMap(int fileDescriptor, boolean delete) {
+ ThreadUtils.assertOnUiThread();
+ if (delete) {
+ sPrintingContextMap.remove(fileDescriptor);
+ } else {
+ sPrintingContextMap.put(fileDescriptor, this);
+ }
+ }
+
+ /**
+ * Notifies the native side that the user just chose a new set of printing settings.
+ * @param success True if the user has chosen printing settings necessary for the
+ * generation of PDF, false if there has been a problem.
+ */
+ @Override
+ public void askUserForSettingsReply(boolean success) {
+ nativeAskUserForSettingsReply(mNativeObject, success);
+ }
+
+ @CalledByNative
+ public static PrintingContext create(Context context, int nativeObjectPointer) {
+ ThreadUtils.assertOnUiThread();
+ return new PrintingContext(context, nativeObjectPointer);
+ }
+
+ @CalledByNative
+ public int getFileDescriptor() {
+ ThreadUtils.assertOnUiThread();
+ return mController.getFileDescriptor();
+ }
+
+ @CalledByNative
+ public int getDpi() {
+ ThreadUtils.assertOnUiThread();
+ return mController.getDpi();
+ }
+
+ @CalledByNative
+ public int getWidth() {
+ ThreadUtils.assertOnUiThread();
+ return mController.getPageWidth();
+ }
+
+ @CalledByNative
+ public int getHeight() {
+ ThreadUtils.assertOnUiThread();
+ return mController.getPageHeight();
+ }
+
+ @CalledByNative
+ public static void pdfWritingDone(int fd, boolean success) {
+ ThreadUtils.assertOnUiThread();
+ // TODO(cimamoglu): Do something when fd == -1.
+ if (sPrintingContextMap.get(fd) != null) {
+ ThreadUtils.assertOnUiThread();
+ PrintingContext printingContext = sPrintingContextMap.get(fd);
+ printingContext.mController.pdfWritingDone(success);
+ sPrintingContextMap.remove(fd);
+ }
+ }
+
+ @CalledByNative
+ public int[] getPages() {
+ ThreadUtils.assertOnUiThread();
+ return mController.getPageNumbers();
+ }
+
+ @CalledByNative
+ public void pageCountEstimationDone(final int maxPages) {
+ ThreadUtils.assertOnUiThread();
+ // If the printing dialog has already finished, tell Chromium that operation is cancelled.
+ if (mController.hasPrintingFinished()) {
+ // NOTE: We don't call nativeAskUserForSettingsReply (hence Chromium callback in
+ // AskUserForSettings callback) twice. See PrintingControllerImpl#onFinish
+ // for more explanation.
+ nativeAskUserForSettingsReply(mNativeObject, false);
+ } else {
+ mController.setPrintingContext(this);
+ mController.pageCountEstimationDone(maxPages);
+ }
+ }
+
+ private native void nativeAskUserForSettingsReply(
+ int nativePrintingContextAndroid,
+ boolean success);
+} \ No newline at end of file
diff --git a/printing/android/java/src/org/chromium/printing/PrintingContextInterface.java b/printing/android/java/src/org/chromium/printing/PrintingContextInterface.java
new file mode 100644
index 0000000..80369b3
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/PrintingContextInterface.java
@@ -0,0 +1,24 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+/**
+ * Defines an interface for PrintingContext.
+ */
+public interface PrintingContextInterface {
+ /**
+ * Updates file descriptor to class instance mapping.
+ * @param fileDescriptor The file descriptor to which the current PrintingContext will be
+ * mapped.
+ * @param delete If true, delete the entry (if it exists). If false, add it to the map.
+ */
+ void updatePrintingContextMap(int fileDescriptor, boolean delete);
+
+ /**
+ * Notifies the native side if the printing settings are successfully prepared.
+ * @param success True if the settings are successfully prepared to be used by the native side.
+ */
+ void askUserForSettingsReply(boolean success);
+}
diff --git a/printing/android/java/src/org/chromium/printing/PrintingController.java b/printing/android/java/src/org/chromium/printing/PrintingController.java
new file mode 100644
index 0000000..beed86b
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/PrintingController.java
@@ -0,0 +1,85 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+/**
+ * This interface describes a class which is responsible of talking to the printing backend.
+ *
+ * Such class communicates with a {@link PrintingContext}, which in turn talks to the native side.
+ */
+public interface PrintingController {
+ /**
+ * @return Dots Per Inch (DPI) of the currently selected printer.
+ */
+ int getDpi();
+
+ /**
+ * @return The file descriptor number of the file into which Chromium will write the PDF. This
+ * is provided to us by {@link PrintDocumentAdapter#onWrite}.
+ */
+ int getFileDescriptor();
+
+ /**
+ * @return The media height in mils (thousands of an inch).
+ */
+ int getPageHeight();
+
+ /**
+ * @return The media width in mils (thousands of an inch).
+ */
+ int getPageWidth();
+
+ /**
+ * @return The individual page numbers of the document to be printed, of null if all pages are
+ * to be printed. The numbers are zero indexed.
+ */
+ int[] getPageNumbers();
+
+ /**
+ * Initiates the printing process for the Android API.
+ *
+ * @param printable An object capable of starting native side PDF generation, i.e. typically
+ * a Tab.
+ */
+ void startPrint(final Printable printable);
+
+ /**
+ * This method is called by the native side to signal PDF writing process is completed.
+ *
+ * @param success Whether the PDF is written into the provided file descriptor successfully.
+ */
+ void pdfWritingDone(boolean success);
+
+ /**
+ * Called when the native side estimates the number of pages in the PDF (before generation).
+ *
+ * @param maxPages Number of pages in the PDF, according to the last provided settings.
+ * If this is PrintDocumentInfo.PAGE_COUNT_UNKNOWN, then use the last known
+ * valid max pages count.
+ */
+ void pageCountEstimationDone(final int maxPages);
+
+ /**
+ * Sets PrintingContext.
+ *
+ * This needs to be called after PrintingContext object is created. Firstly its native
+ * counterpart is created, and then the Java. PrintingController implementation
+ * needs this to interact with the native side, since JNI is built on PrintingContext.
+ **/
+ void setPrintingContext(final PrintingContextInterface printingContext);
+
+ /**
+ * TODO(cimamoglu): Remove errorText stuff once KitKat is public and we can move this code.
+ * @param errorText The error message to be shown to user in case something goes wrong in PDF
+ * generation in Chromium. We pass it here as a string because this folder
+ * cannot use resources directly (or any other Clank code).
+ */
+ void setErrorText(final String errorText);
+
+ /**
+ * @return Whether a complete PDF generation cycle inside Chromium has been completed.
+ */
+ boolean hasPrintingFinished();
+}
diff --git a/printing/android/java/src/org/chromium/printing/PrintingControllerFactory.java b/printing/android/java/src/org/chromium/printing/PrintingControllerFactory.java
new file mode 100644
index 0000000..78c67db
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/PrintingControllerFactory.java
@@ -0,0 +1,33 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+import org.chromium.base.ThreadUtils;
+
+import android.content.Context;
+
+public class PrintingControllerFactory {
+ /** The singleton instance for this class. */
+ private static PrintingController sInstance;
+
+ private PrintingControllerFactory() {} // Static factory
+
+ /**
+ * Creates a controller for handling printing with the framework.
+ *
+ * The controller is a singleton, since there can be only one printing action at any time.
+ *
+ * @param context The application context.
+ * @return The resulting PrintingController.
+ */
+ public static PrintingController getPrintingController(
+ Context context) {
+ ThreadUtils.assertOnUiThread();
+ if (sInstance == null) {
+ sInstance = new PrintingControllerImpl(context);
+ }
+ return sInstance;
+ }
+}
diff --git a/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java b/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java
new file mode 100644
index 0000000..34c4dc4
--- /dev/null
+++ b/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java
@@ -0,0 +1,349 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.printing;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.CancellationSignal.OnCancelListener;
+import android.print.PageRange;
+import android.print.PrintAttributes;
+import android.print.PrintAttributes.MediaSize;
+import android.print.PrintAttributes.Resolution;
+import android.print.PrintDocumentAdapter;
+import android.print.PrintDocumentAdapter.LayoutResultCallback;
+import android.print.PrintDocumentAdapter.WriteResultCallback;
+import android.print.PrintDocumentInfo;
+import android.print.PrintJob;
+import android.print.PrintManager;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Controls the interactions with Android framework related to printing.
+ *
+ * This class is singleton, since at any point at most one printing dialog can exist. Also, since
+ * this dialog is modal, user can't interact with browser unless s/he closes the dialog or presses
+ * print button. The singleton object lives in UI thread. Interaction with the native side is
+ * carried through PrintingContext class.
+ */
+class PrintingControllerImpl extends PrintDocumentAdapter implements PrintingController {
+
+ private static final String LOG_TAG = "PrintingControllerImpl";
+
+ private static final String PDF_FILE_NAME = "chrome_print_document.pdf";
+
+ private String mErrorMessage;
+
+ private final PrintManager mPrintManager;
+
+ private PrintingContextInterface mPrintingContext;
+
+ /** The file descriptor into which the PDF will be written. Provided by the framework. */
+ private int mFileDescriptor;
+
+ /** Dots per inch, as provided by the framework. */
+ private int mDpi;
+
+ /** Paper dimensions. */
+ private PrintAttributes.MediaSize mMediaSize;
+
+ /** Numbers of pages to be printed, zero indexed. */
+ private int[] mPages;
+
+ /** The callback function to inform the result of PDF generation to the framework. */
+ private PrintDocumentAdapter.WriteResultCallback mOnWriteCallback;
+
+ /**
+ * The callback function to inform the result of layout to the framework. We save the callback
+ * because we start the native PDF generation process inside onLayout, and we need to pass the
+ * number of expected pages back to the framework through this callback once the native side
+ * has that information.
+ */
+ private PrintDocumentAdapter.LayoutResultCallback mOnLayoutCallback;
+
+ /** The object through which native PDF generation process is initiated. */
+ private Printable mPrintable;
+
+ /**
+ * This is used for both initial state and a completed state (i.e. starting from either
+ * onLayout or onWrite, a PDF generation cycle is completed another new one can safely start).
+ */
+ private final static int PRINTING_STATE_READY = 0;
+ private final static int PRINTING_STATE_STARTED_FROM_ONLAYOUT = 1;
+ private final static int PRINTING_STATE_STARTED_FROM_ONWRITE = 2;
+ /** Printing dialog has been dismissed and cleanup has been done. */
+ private final static int PRINTING_STATE_FINISHED = 3;
+
+ private int mPrintingState = PRINTING_STATE_READY;
+
+ /** Whether layouting parameters have been changed to require a new PDF generation. */
+ private boolean mNeedNewPdf = false;
+
+ /** Total number of pages to print with initial print dialog settings. */
+ private int mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+
+ PrintingControllerImpl(final Context context) {
+ mPrintManager = (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
+ }
+
+ @Override
+ public boolean hasPrintingFinished() {
+ return mPrintingState == PRINTING_STATE_FINISHED;
+ }
+
+ @Override
+ public void setErrorText(final String errorText) {
+ mErrorMessage = errorText;
+ }
+
+ @Override
+ public int getDpi() {
+ return mDpi;
+ }
+
+ @Override
+ public int getFileDescriptor() {
+ return mFileDescriptor;
+ }
+
+ @Override
+ public int getPageHeight() {
+ return mMediaSize.getHeightMils();
+ }
+
+ @Override
+ public int getPageWidth() {
+ return mMediaSize.getWidthMils();
+ }
+
+ @Override
+ public int[] getPageNumbers() {
+ return mPages;
+ }
+
+ @Override
+ public void setPrintingContext(final PrintingContextInterface printingContext) {
+ mPrintingContext = printingContext;
+ }
+
+ @Override
+ public void startPrint(final Printable printable) {
+ mPrintable = printable;
+ mPrintManager.print(printable.getTitle(), this, null);
+ }
+
+ @Override
+ public void pdfWritingDone(boolean success) {
+ if (mPrintingState == PRINTING_STATE_FINISHED) return;
+ mPrintingState = PRINTING_STATE_READY;
+ if (success) {
+ PageRange[] pageRanges = convertIntegerArrayToPageRanges(mPages);
+ mOnWriteCallback.onWriteFinished(pageRanges);
+ } else {
+ mOnWriteCallback.onWriteFailed(mErrorMessage);
+ resetCallbacks();
+ }
+ closeFileDescriptor(mFileDescriptor);
+ mFileDescriptor = -1;
+ }
+
+ @Override
+ public void onStart() {
+ mPrintingState = PRINTING_STATE_READY;
+ }
+
+ @Override
+ public void onLayout(PrintAttributes oldAttributes,
+ PrintAttributes newAttributes, CancellationSignal cancellationSignal,
+ LayoutResultCallback callback, Bundle metadata) {
+ // NOTE: Chrome printing just supports one DPI, whereas Android has both vertical and
+ // horizontal. These two values are most of the time same, so we just pass one of them.
+ mDpi = newAttributes.getResolution().getHorizontalDpi();
+ mMediaSize = newAttributes.getMediaSize();
+
+ mNeedNewPdf = !newAttributes.equals(oldAttributes);
+
+ mOnLayoutCallback = callback;
+ // We don't want to stack Chromium with multiple PDF generation operations before
+ // completion of an ongoing one.
+ // TODO(cimamoglu): Whenever onLayout is called, generate a new PDF with the new
+ // parameters. Hence, we can get the true number of pages.
+ if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
+ // We don't start a new Chromium PDF generation operation if there's an existing
+ // onLayout going on. Use the last known valid page count.
+ pageCountEstimationDone(mLastKnownMaxPages);
+ } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONWRITE) {
+ callback.onLayoutFailed(mErrorMessage);
+ resetCallbacks();
+ } else if (mPrintable.print()) {
+ mPrintingState = PRINTING_STATE_STARTED_FROM_ONLAYOUT;
+ } else {
+ callback.onLayoutFailed(mErrorMessage);
+ resetCallbacks();
+ }
+ }
+
+ @Override
+ public void pageCountEstimationDone(final int maxPages) {
+ // This method might be called even after onFinish, e.g. as a result of a long page
+ // estimation operation. We make sure that such call has no effect, since the printing
+ // dialog has already been dismissed and relevant cleanup has already been done.
+ // Also, this ensures that we do not call askUserForSettingsReply twice.
+ if (mPrintingState == PRINTING_STATE_FINISHED) return;
+ if (maxPages != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
+ mLastKnownMaxPages = maxPages;
+ }
+ if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
+ // TODO(cimamoglu): Choose a meaningful filename.
+ PrintDocumentInfo info = new PrintDocumentInfo.Builder(PDF_FILE_NAME)
+ .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
+ .setPageCount(mLastKnownMaxPages)
+ .build();
+ mOnLayoutCallback.onLayoutFinished(info, mNeedNewPdf);
+ } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONWRITE) {
+ // Chromium PDF generation is started inside onWrite, continue that.
+ if (mPrintingContext == null) {
+ mOnWriteCallback.onWriteFailed(mErrorMessage);
+ resetCallbacks();
+ return;
+ }
+ mPrintingContext.askUserForSettingsReply(true);
+ }
+ }
+
+ @Override
+ public void onWrite(
+ final PageRange[] ranges,
+ final ParcelFileDescriptor destination,
+ final CancellationSignal cancellationSignal,
+ final WriteResultCallback callback) {
+ if (mPrintingContext == null) {
+ callback.onWriteFailed(mErrorMessage);
+ resetCallbacks();
+ return;
+ }
+
+ // TODO(cimamoglu): Make use of CancellationSignal.
+ mOnWriteCallback = callback;
+
+ mFileDescriptor = destination.getFd();
+ // Update file descriptor to PrintingContext mapping in the owner class.
+ mPrintingContext.updatePrintingContextMap(mFileDescriptor, false);
+
+ // We need to convert ranges list into an array of individual numbers for
+ // easier JNI passing and compatibility with the native side.
+ if (ranges.length == 1 && ranges[0].equals(PageRange.ALL_PAGES)) {
+ // null corresponds to all pages in Chromium printing logic.
+ mPages = null;
+ } else {
+ mPages = normalizeRanges(ranges);
+ }
+
+ if (mPrintingState == PRINTING_STATE_READY) {
+ // If this onWrite is without a preceding onLayout, start Chromium PDF generation here.
+ if (mPrintable.print()) {
+ mPrintingState = PRINTING_STATE_STARTED_FROM_ONWRITE;
+ } else {
+ callback.onWriteFailed(mErrorMessage);
+ resetCallbacks();
+ }
+ } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
+ // Otherwise, continue previously started operation.
+ mPrintingContext.askUserForSettingsReply(true);
+ }
+ // We are guaranteed by the framework that we will not have two onWrite calls at once.
+ // We may get a CancellationSignal, after replying it (via WriteResultCallback) we might
+ // get another onWrite call.
+ }
+
+ @Override
+ public void onFinish() {
+ super.onFinish();
+ mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
+ mPages = null;
+
+ if (mPrintingContext != null) {
+ if (mPrintingState != PRINTING_STATE_READY) {
+ // Note that we are never making an extraneous askUserForSettingsReply call.
+ // If we are in the middle of a PDF generation from onLayout or onWrite, it means
+ // the state isn't PRINTING_STATE_READY, so we enter here and make this call (no
+ // extra). If we complete the PDF generation successfully from onLayout or onWrite,
+ // we already make the state PRINTING_STATE_READY and call askUserForSettingsReply
+ // inside pdfWritingDone, thus not entering here. Also, if we get an extra
+ // AskUserForSettings call, it's handled inside {@link
+ // PrintingContext#pageCountEstimationDone}.
+ mPrintingContext.askUserForSettingsReply(false);
+ }
+ mPrintingContext.updatePrintingContextMap(mFileDescriptor, true);
+ mPrintingContext = null;
+ }
+ mPrintingState = PRINTING_STATE_FINISHED;
+
+ closeFileDescriptor(mFileDescriptor);
+ mFileDescriptor = -1;
+
+ resetCallbacks();
+ }
+
+ private void resetCallbacks() {
+ mOnWriteCallback = null;
+ mOnLayoutCallback = null;
+ }
+
+ private void closeFileDescriptor(int fd) {
+ if (fd != -1) return;
+ ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.adoptFd(fd);
+ if (fileDescriptor != null) {
+ try {
+ fileDescriptor.close();
+ } catch (IOException ioe) {
+ /* ignore */
+ }
+ }
+ }
+
+ private PageRange[] convertIntegerArrayToPageRanges(int[] pagesArray) {
+ PageRange[] pageRanges;
+ if (pagesArray != null) {
+ pageRanges = new PageRange[pagesArray.length];
+ for (int i = 0; i < pageRanges.length; i++) {
+ int page = pagesArray[i];
+ pageRanges[i] = new PageRange(page, page);
+ }
+ } else {
+ // null corresponds to all pages in Chromium printing logic.
+ pageRanges = new PageRange[] { PageRange.ALL_PAGES };
+ }
+ return pageRanges;
+ }
+
+ /**
+ * Gets an array of page ranges and returns an array of integers with all ranges expanded.
+ */
+ private int[] normalizeRanges(final PageRange[] ranges) {
+ // Expand ranges into a list of individual numbers.
+ ArrayList<Integer> pages = new ArrayList<Integer>();
+ for (PageRange range : ranges) {
+ for (int i = range.getStart(); i <= range.getEnd(); i++) {
+ pages.add(i);
+ }
+ }
+
+ // Convert the list into array.
+ int[] ret = new int[pages.size()];
+ Iterator<Integer> iterator = pages.iterator();
+ for (int i = 0; i < ret.length; i++) {
+ ret[i] = iterator.next().intValue();
+ }
+ return ret;
+ }
+}
diff --git a/printing/printing.gyp b/printing/printing.gyp
index 1918657..24b5684 100644
--- a/printing/printing.gyp
+++ b/printing/printing.gyp
@@ -229,8 +229,15 @@
}],
['OS=="android"', {
'sources': [
+ 'printing_context_android.cc',
'printing_context_android.h',
],
+ 'dependencies': [
+ 'printing_jni_headers',
+ ],
+ 'include_dirs': [
+ '<(SHARED_INTERMEDIATE_DIR)/printing',
+ ],
}],
],
},
@@ -321,4 +328,32 @@
],
},
],
+ 'conditions': [
+ ['OS == "android"', {
+ 'targets': [
+ {
+ 'target_name': 'printing_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/src/org/chromium/printing/PrintingContext.java',
+ ],
+ 'variables': {
+ 'jni_gen_package': 'printing',
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
+ {
+ 'target_name': 'printing_java',
+ 'type': 'none',
+ 'variables': {
+ 'java_in_dir': '../printing/android/java',
+ },
+ 'dependencies': [
+ '../base/base.gyp:base_java',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ }
+ ]
+ }],
+ ]
}
diff --git a/printing/printing_context_android.cc b/printing/printing_context_android.cc
new file mode 100644
index 0000000..1acf183
--- /dev/null
+++ b/printing/printing_context_android.cc
@@ -0,0 +1,246 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "printing/printing_context_android.h"
+
+#include <vector>
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/values.h"
+#include "jni/PrintingContext_jni.h"
+#include "printing/metafile.h"
+#include "printing/print_job_constants.h"
+#include "printing/units.h"
+#include "third_party/icu/source/i18n/unicode/ulocdata.h"
+
+namespace {
+
+// 1 inch in mils.
+const int kInchToMil = 1000;
+
+inline int Round(double x) {
+ return static_cast<int>(x + 0.5);
+}
+
+// Sets the page sizes for a |PrintSettings| object. |width| and |height|
+// arguments should be in device units.
+void SetSizes(
+ printing::PrintSettings* settings, int dpi, int width, int height) {
+ gfx::Size physical_size_device_units(width, height);
+ // Assume full page is printable for now.
+ gfx::Rect printable_area_device_units(0, 0, width, height);
+
+ settings->set_dpi(dpi);
+ settings->SetPrinterPrintableArea(physical_size_device_units,
+ printable_area_device_units,
+ false);
+}
+
+void GetPageRanges(JNIEnv* env,
+ jintArray int_arr,
+ printing::PageRanges& range_vector) {
+ std::vector<int> pages;
+ base::android::JavaIntArrayToIntVector(env, int_arr, &pages);
+ for (std::vector<int>::const_iterator it = pages.begin();
+ it != pages.end();
+ ++it) {
+ printing::PageRange range;
+ range.from = *it;
+ range.to = *it;
+ range_vector.push_back(range);
+ }
+}
+
+} // namespace
+
+namespace printing {
+
+// static
+PrintingContext* PrintingContext::Create(const std::string& app_locale) {
+ return new PrintingContextAndroid(app_locale);
+}
+
+// static
+void PrintingContextAndroid::PdfWritingDone(int fd, bool success) {
+ JNIEnv* env = base::android::AttachCurrentThread();
+ Java_PrintingContext_pdfWritingDone(env, fd, success);
+}
+
+PrintingContextAndroid::PrintingContextAndroid(const std::string& app_locale)
+ : PrintingContext(app_locale) {
+ // The constructor is run in the IO thread.
+}
+
+PrintingContextAndroid::~PrintingContextAndroid() {
+}
+
+void PrintingContextAndroid::AskUserForSettings(
+ gfx::NativeView parent_view,
+ int max_pages,
+ bool has_selection,
+ const PrintSettingsCallback& callback) {
+ // This method is always run in the UI thread.
+ callback_ = callback;
+
+ JNIEnv* env = base::android::AttachCurrentThread();
+ if (j_printing_context_.is_null()) {
+ j_printing_context_.Reset(Java_PrintingContext_create(
+ env,
+ base::android::GetApplicationContext(),
+ reinterpret_cast<int>(this)));
+ }
+
+ Java_PrintingContext_pageCountEstimationDone(env,
+ j_printing_context_.obj(),
+ max_pages);
+}
+
+void PrintingContextAndroid::AskUserForSettingsReply(JNIEnv* env,
+ jobject obj,
+ jboolean success) {
+ if (!success) {
+ // TODO(cimamoglu): Differentiate between FAILED And CANCEL.
+ callback_.Run(FAILED);
+ return;
+ }
+
+ // We use device name variable to store the file descriptor. This is hacky
+ // but necessary. Since device name is not necessary for the upstream
+ // printing code for Android, this is harmless.
+ int fd = Java_PrintingContext_getFileDescriptor(env,
+ j_printing_context_.obj());
+ settings_.set_device_name(base::IntToString16(fd));
+
+ ScopedJavaLocalRef<jintArray> intArr =
+ Java_PrintingContext_getPages(env, j_printing_context_.obj());
+ if (intArr.obj() != NULL) {
+ PageRanges range_vector;
+ GetPageRanges(env, intArr.obj(), range_vector);
+ settings_.set_ranges(range_vector);
+ }
+
+ int dpi = Java_PrintingContext_getDpi(env, j_printing_context_.obj());
+ int width = Java_PrintingContext_getWidth(env, j_printing_context_.obj());
+ int height = Java_PrintingContext_getHeight(env, j_printing_context_.obj());
+ width = Round(ConvertUnitDouble(width, kInchToMil, 1.0) * dpi);
+ height = Round(ConvertUnitDouble(height, kInchToMil, 1.0) * dpi);
+ SetSizes(&settings_, dpi, width, height);
+
+ callback_.Run(OK);
+}
+
+PrintingContext::Result PrintingContextAndroid::UseDefaultSettings() {
+ DCHECK(!in_print_job_);
+
+ ResetSettings();
+ gfx::Size physical_size = GetPdfPaperSizeDeviceUnits();
+ SetSizes(&settings_, kDefaultPdfDpi, physical_size.width(),
+ physical_size.height());
+ return OK;
+}
+
+gfx::Size PrintingContextAndroid::GetPdfPaperSizeDeviceUnits() {
+ // NOTE: This implementation is the same as in PrintingContextNoSystemDialog.
+ int32_t width = 0;
+ int32_t height = 0;
+ UErrorCode error = U_ZERO_ERROR;
+ ulocdata_getPaperSize(app_locale_.c_str(), &height, &width, &error);
+ if (error > U_ZERO_ERROR) {
+ // If the call failed, assume a paper size of 8.5 x 11 inches.
+ LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: "
+ << error;
+ width = static_cast<int>(
+ kLetterWidthInch * settings_.device_units_per_inch());
+ height = static_cast<int>(
+ kLetterHeightInch * settings_.device_units_per_inch());
+ } else {
+ // ulocdata_getPaperSize returns the width and height in mm.
+ // Convert this to pixels based on the dpi.
+ float multiplier = 100 * settings_.device_units_per_inch();
+ multiplier /= kHundrethsMMPerInch;
+ width *= multiplier;
+ height *= multiplier;
+ }
+ return gfx::Size(width, height);
+}
+
+PrintingContext::Result PrintingContextAndroid::UpdatePrinterSettings(
+ bool external_preview) {
+ DCHECK(!in_print_job_);
+
+ // Intentional No-op.
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContextAndroid::InitWithSettings(
+ const PrintSettings& settings) {
+ DCHECK(!in_print_job_);
+
+ settings_ = settings;
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContextAndroid::NewDocument(
+ const string16& document_name) {
+ DCHECK(!in_print_job_);
+ in_print_job_ = true;
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContextAndroid::NewPage() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ // Intentional No-op.
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContextAndroid::PageDone() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ // Intentional No-op.
+
+ return OK;
+}
+
+PrintingContext::Result PrintingContextAndroid::DocumentDone() {
+ if (abort_printing_)
+ return CANCEL;
+ DCHECK(in_print_job_);
+
+ ResetSettings();
+ return OK;
+}
+
+void PrintingContextAndroid::Cancel() {
+ abort_printing_ = true;
+ in_print_job_ = false;
+}
+
+void PrintingContextAndroid::ReleaseContext() {
+ // Intentional No-op.
+}
+
+gfx::NativeDrawingContext PrintingContextAndroid::context() const {
+ // Intentional No-op.
+ return NULL;
+}
+
+// static
+bool PrintingContextAndroid::RegisterPrintingContext(JNIEnv* env) {
+ return RegisterNativesImpl(env);
+}
+
+} // namespace printing