1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.printing;
7 import android.os.Bundle;
8 import android.os.CancellationSignal;
9 import android.os.ParcelFileDescriptor;
10 import android.print.PageRange;
11 import android.print.PrintAttributes;
12 import android.print.PrintDocumentInfo;
14 import org.chromium.base.ThreadUtils;
15 import org.chromium.printing.PrintDocumentAdapterWrapper.PdfGenerator;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Iterator;
22 * Controls the interactions with Android framework related to printing.
24 * This class is singleton, since at any point at most one printing dialog can exist. Also, since
25 * this dialog is modal, user can't interact with browser unless s/he closes the dialog or presses
26 * print button. The singleton object lives in UI thread. Interaction with the native side is
27 * carried through PrintingContext class.
29 public class PrintingControllerImpl implements PrintingController, PdfGenerator {
31 private static final String LOG_TAG = "PrintingControllerImpl";
34 * This is used for both initial state and a completed state (i.e. starting from either
35 * onLayout or onWrite, a PDF generation cycle is completed another new one can safely start).
37 private static final int PRINTING_STATE_READY = 0;
38 private static final int PRINTING_STATE_STARTED_FROM_ONLAYOUT = 1;
39 private static final int PRINTING_STATE_STARTED_FROM_ONWRITE = 2;
40 /** Printing dialog has been dismissed and cleanup has been done. */
41 private static final int PRINTING_STATE_FINISHED = 3;
43 /** The singleton instance for this class. */
44 private static PrintingController sInstance;
46 private final String mErrorMessage;
48 private PrintingContextInterface mPrintingContext;
50 /** The file descriptor into which the PDF will be written. Provided by the framework. */
51 private int mFileDescriptor;
53 /** Dots per inch, as provided by the framework. */
56 /** Paper dimensions. */
57 private PrintAttributes.MediaSize mMediaSize;
59 /** Numbers of pages to be printed, zero indexed. */
62 /** The callback function to inform the result of PDF generation to the framework. */
63 private PrintDocumentAdapterWrapper.WriteResultCallbackWrapper mOnWriteCallback;
66 * The callback function to inform the result of layout to the framework. We save the callback
67 * because we start the native PDF generation process inside onLayout, and we need to pass the
68 * number of expected pages back to the framework through this callback once the native side
69 * has that information.
71 private PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper mOnLayoutCallback;
73 /** The object through which native PDF generation process is initiated. */
74 private Printable mPrintable;
76 /** The object through which the framework will make calls for generating PDF. */
77 private PrintDocumentAdapterWrapper mPrintDocumentAdapterWrapper;
79 private int mPrintingState = PRINTING_STATE_READY;
81 /** Whether layouting parameters have been changed to require a new PDF generation. */
82 private boolean mNeedNewPdf = false;
84 /** Total number of pages to print with initial print dialog settings. */
85 private int mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
87 private boolean mIsBusy = false;
89 private PrintingControllerImpl(PrintDocumentAdapterWrapper printDocumentAdapterWrapper,
91 mErrorMessage = errorText;
92 mPrintDocumentAdapterWrapper = printDocumentAdapterWrapper;
93 mPrintDocumentAdapterWrapper.setPdfGenerator(this);
97 * Creates a controller for handling printing with the framework.
99 * The controller is a singleton, since there can be only one printing action at any time.
101 * @param printDocumentAdapterWrapper The object through which the framework will make calls
102 * for generating PDF.
103 * @param errorText The error message to be shown to user in case something goes wrong in PDF
104 * generation in Chromium. We pass it here as a string so src/printing/android
105 * doesn't need any string dependency.
106 * @return The resulting PrintingController.
108 public static PrintingController create(
109 PrintDocumentAdapterWrapper printDocumentAdapterWrapper, String errorText) {
110 ThreadUtils.assertOnUiThread();
112 if (sInstance == null) {
113 sInstance = new PrintingControllerImpl(printDocumentAdapterWrapper, errorText);
119 * Returns the singleton instance, created by the {@link PrintingControllerImpl#create}.
121 * This method must be called once {@link PrintingControllerImpl#create} is called, and always
124 * @return The singleton instance.
126 public static PrintingController getInstance() {
131 public boolean hasPrintingFinished() {
132 return mPrintingState == PRINTING_STATE_FINISHED;
136 public int getDpi() {
141 public int getFileDescriptor() {
142 return mFileDescriptor;
146 public int getPageHeight() {
147 return mMediaSize.getHeightMils();
151 public int getPageWidth() {
152 return mMediaSize.getWidthMils();
156 public int[] getPageNumbers() {
157 return mPages == null ? null : mPages.clone();
161 public boolean isBusy() {
166 public void setPrintingContext(final PrintingContextInterface printingContext) {
167 mPrintingContext = printingContext;
171 public void startPrint(final Printable printable, PrintManagerDelegate printManager) {
174 mPrintable = printable;
175 mPrintDocumentAdapterWrapper.print(printManager, printable.getTitle());
179 public void pdfWritingDone(boolean success) {
180 if (mPrintingState == PRINTING_STATE_FINISHED) return;
181 mPrintingState = PRINTING_STATE_READY;
183 PageRange[] pageRanges = convertIntegerArrayToPageRanges(mPages);
184 mOnWriteCallback.onWriteFinished(pageRanges);
186 mOnWriteCallback.onWriteFailed(mErrorMessage);
189 closeFileDescriptor(mFileDescriptor);
190 mFileDescriptor = -1;
194 public void onStart() {
195 mPrintingState = PRINTING_STATE_READY;
199 public void onLayout(
200 PrintAttributes oldAttributes,
201 PrintAttributes newAttributes,
202 CancellationSignal cancellationSignal,
203 PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper callback,
205 // NOTE: Chrome printing just supports one DPI, whereas Android has both vertical and
206 // horizontal. These two values are most of the time same, so we just pass one of them.
207 mDpi = newAttributes.getResolution().getHorizontalDpi();
208 mMediaSize = newAttributes.getMediaSize();
210 mNeedNewPdf = !newAttributes.equals(oldAttributes);
212 mOnLayoutCallback = callback;
213 // We don't want to stack Chromium with multiple PDF generation operations before
214 // completion of an ongoing one.
215 // TODO(cimamoglu): Whenever onLayout is called, generate a new PDF with the new
216 // parameters. Hence, we can get the true number of pages.
217 if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
218 // We don't start a new Chromium PDF generation operation if there's an existing
219 // onLayout going on. Use the last known valid page count.
220 pageCountEstimationDone(mLastKnownMaxPages);
221 } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONWRITE) {
222 callback.onLayoutFailed(mErrorMessage);
224 } else if (mPrintable.print()) {
225 mPrintingState = PRINTING_STATE_STARTED_FROM_ONLAYOUT;
227 callback.onLayoutFailed(mErrorMessage);
233 public void pageCountEstimationDone(final int maxPages) {
234 // This method might be called even after onFinish, e.g. as a result of a long page
235 // estimation operation. We make sure that such call has no effect, since the printing
236 // dialog has already been dismissed and relevant cleanup has already been done.
237 // Also, this ensures that we do not call askUserForSettingsReply twice.
238 if (mPrintingState == PRINTING_STATE_FINISHED) return;
239 if (maxPages != PrintDocumentInfo.PAGE_COUNT_UNKNOWN) {
240 mLastKnownMaxPages = maxPages;
242 if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
243 PrintDocumentInfo info = new PrintDocumentInfo.Builder(mPrintable.getTitle())
244 .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
245 .setPageCount(mLastKnownMaxPages)
247 mOnLayoutCallback.onLayoutFinished(info, mNeedNewPdf);
248 } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONWRITE) {
249 // Chromium PDF generation is started inside onWrite, continue that.
250 if (mPrintingContext == null) {
251 mOnWriteCallback.onWriteFailed(mErrorMessage);
255 mPrintingContext.askUserForSettingsReply(true);
261 final PageRange[] ranges,
262 final ParcelFileDescriptor destination,
263 final CancellationSignal cancellationSignal,
264 final PrintDocumentAdapterWrapper.WriteResultCallbackWrapper callback) {
265 if (mPrintingContext == null) {
266 callback.onWriteFailed(mErrorMessage);
271 // TODO(cimamoglu): Make use of CancellationSignal.
272 mOnWriteCallback = callback;
274 mFileDescriptor = destination.getFd();
275 // Update file descriptor to PrintingContext mapping in the owner class.
276 mPrintingContext.updatePrintingContextMap(mFileDescriptor, false);
278 // We need to convert ranges list into an array of individual numbers for
279 // easier JNI passing and compatibility with the native side.
280 if (ranges.length == 1 && ranges[0].equals(PageRange.ALL_PAGES)) {
281 // null corresponds to all pages in Chromium printing logic.
284 mPages = normalizeRanges(ranges);
287 if (mPrintingState == PRINTING_STATE_READY) {
288 // If this onWrite is without a preceding onLayout, start Chromium PDF generation here.
289 if (mPrintable.print()) {
290 mPrintingState = PRINTING_STATE_STARTED_FROM_ONWRITE;
292 callback.onWriteFailed(mErrorMessage);
295 } else if (mPrintingState == PRINTING_STATE_STARTED_FROM_ONLAYOUT) {
296 // Otherwise, continue previously started operation.
297 mPrintingContext.askUserForSettingsReply(true);
299 // We are guaranteed by the framework that we will not have two onWrite calls at once.
300 // We may get a CancellationSignal, after replying it (via WriteResultCallback) we might
301 // get another onWrite call.
305 public void onFinish() {
306 mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN;
309 if (mPrintingContext != null) {
310 if (mPrintingState != PRINTING_STATE_READY) {
311 // Note that we are never making an extraneous askUserForSettingsReply call.
312 // If we are in the middle of a PDF generation from onLayout or onWrite, it means
313 // the state isn't PRINTING_STATE_READY, so we enter here and make this call (no
314 // extra). If we complete the PDF generation successfully from onLayout or onWrite,
315 // we already make the state PRINTING_STATE_READY and call askUserForSettingsReply
316 // inside pdfWritingDone, thus not entering here. Also, if we get an extra
317 // AskUserForSettings call, it's handled inside {@link
318 // PrintingContext#pageCountEstimationDone}.
319 mPrintingContext.askUserForSettingsReply(false);
321 mPrintingContext.updatePrintingContextMap(mFileDescriptor, true);
322 mPrintingContext = null;
324 mPrintingState = PRINTING_STATE_FINISHED;
326 closeFileDescriptor(mFileDescriptor);
327 mFileDescriptor = -1;
330 // The printmanager contract is that onFinish() is always called as the last
331 // callback. We set busy to false here.
335 private void resetCallbacks() {
336 mOnWriteCallback = null;
337 mOnLayoutCallback = null;
340 private static void closeFileDescriptor(int fd) {
341 if (fd != -1) return;
342 ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.adoptFd(fd);
343 if (fileDescriptor != null) {
345 fileDescriptor.close();
346 } catch (IOException ioe) {
352 private static PageRange[] convertIntegerArrayToPageRanges(int[] pagesArray) {
353 PageRange[] pageRanges;
354 if (pagesArray != null) {
355 pageRanges = new PageRange[pagesArray.length];
356 for (int i = 0; i < pageRanges.length; i++) {
357 int page = pagesArray[i];
358 pageRanges[i] = new PageRange(page, page);
361 // null corresponds to all pages in Chromium printing logic.
362 pageRanges = new PageRange[] { PageRange.ALL_PAGES };
368 * Gets an array of page ranges and returns an array of integers with all ranges expanded.
370 private static int[] normalizeRanges(final PageRange[] ranges) {
371 // Expand ranges into a list of individual numbers.
372 ArrayList<Integer> pages = new ArrayList<Integer>();
373 for (PageRange range : ranges) {
374 for (int i = range.getStart(); i <= range.getEnd(); i++) {
379 // Convert the list into array.
380 int[] ret = new int[pages.size()];
381 Iterator<Integer> iterator = pages.iterator();
382 for (int i = 0; i < ret.length; i++) {
383 ret[i] = iterator.next().intValue();