diff options
author | cimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 17:29:24 +0000 |
---|---|---|
committer | cimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-11-14 17:29:24 +0000 |
commit | b6f76a1ae51b531ef1daabd9bd05fd8c87e61cd4 (patch) | |
tree | 911fce788076849f876b6fcb2a297edecd053451 /printing | |
parent | b50d4e7248ebd524b7a1e07b4cdf232ec54f0f1e (diff) | |
download | chromium_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')
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 |