diff options
author | cimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-04 21:28:54 +0000 |
---|---|---|
committer | cimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-12-04 21:28:54 +0000 |
commit | 334c6d06ca072128348ee8e0f39a687b8ac14b5e (patch) | |
tree | 5224785514b7475bae66f84a39e3ee209041fa84 | |
parent | cc5e8f152817ca6102d0f15d5c3a5452e04553f1 (diff) | |
download | chromium_src-334c6d06ca072128348ee8e0f39a687b8ac14b5e.zip chromium_src-334c6d06ca072128348ee8e0f39a687b8ac14b5e.tar.gz chromium_src-334c6d06ca072128348ee8e0f39a687b8ac14b5e.tar.bz2 |
Android: refactors printing code & adds a test
Review URL: https://codereview.chromium.org/85693005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@238776 0039d316-1c4b-4281-b951-d872f2087c98
4 files changed, 418 insertions, 16 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintingControllerFactory.java b/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintingControllerFactory.java index 9603453..03b1198 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintingControllerFactory.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/printing/PrintingControllerFactory.java @@ -10,6 +10,7 @@ import android.print.PrintManager; import org.chromium.base.ApiCompatibilityUtils; import org.chromium.chrome.R; +import org.chromium.printing.PrintDocumentAdapterWrapper; import org.chromium.printing.PrintManagerDelegateImpl; import org.chromium.printing.PrintingController; import org.chromium.printing.PrintingControllerImpl; @@ -28,8 +29,8 @@ public class PrintingControllerFactory { PrintManager printManager = (PrintManager) activity.getSystemService(Context.PRINT_SERVICE); String errorText = activity.getResources().getString(R.string.error_printing_failed); - return PrintingControllerImpl.create( - new PrintManagerDelegateImpl(printManager), errorText); + return PrintingControllerImpl.create(new PrintManagerDelegateImpl(printManager), + new PrintDocumentAdapterWrapper(), errorText); } return null; } diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java new file mode 100644 index 0000000..1ee880d --- /dev/null +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java @@ -0,0 +1,245 @@ +// 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.os.CancellationSignal; +import android.os.ParcelFileDescriptor; +import android.print.PageRange; +import android.print.PrintAttributes; +import android.print.PrintDocumentAdapter; +import android.print.PrintDocumentInfo; + +import org.chromium.base.ApiCompatibilityUtils; +import org.chromium.base.test.util.Feature; +import org.chromium.base.test.util.TestFileUtil; +import org.chromium.base.test.util.UrlUtils; +import org.chromium.chrome.browser.printing.TabPrinter; +import org.chromium.chrome.testshell.ChromiumTestShellTestBase; +import org.chromium.chrome.testshell.TestShellTab; +import org.chromium.printing.PrintDocumentAdapterWrapper; +import org.chromium.printing.PrintManagerDelegate; +import org.chromium.printing.Printable; +import org.chromium.printing.PrintingControllerImpl; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import android.test.suitebuilder.annotation.LargeTest; + +/** + * Tests Android printing. + * TODO(cimamoglu): Add a test with cancellation. + * TODO(cimamoglu): Add a test with multiple, stacked onLayout/onWrite calls. + * TODO(cimamoglu): Add a test which emulates Chromium failing to generate a PDF. + */ +public class PrintingControllerTest extends ChromiumTestShellTestBase { + + private static final String TEMP_FILE_NAME = "temp_print"; + private static final String TEMP_FILE_EXTENSION = ".pdf"; + private static final String PRINT_JOB_NAME = "foo"; + private static final String URL = UrlUtils.encodeHtmlDataUri( + "<html><head></head><body>foo</body></html>"); + private static final String PDF_PREAMBLE = "%PDF-1"; + private static long TEST_TIMEOUT = 20000L; + + private static class LayoutResultCallbackWrapperMock implements + PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper { + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {} + + @Override + public void onLayoutFailed(CharSequence error) {} + + @Override + public void onLayoutCancelled() {} + } + + private static class WriteResultCallbackWrapperMock implements + PrintDocumentAdapterWrapper.WriteResultCallbackWrapper { + @Override + public void onWriteFinished(PageRange[] pages) {} + + @Override + public void onWriteFailed(CharSequence error) {} + + @Override + public void onWriteCancelled() {} + } + + /** + * Test a basic printing flow by emulating the corresponding system calls to the printing + * controller: onStart, onLayout, onWrite, onFinish. Each one is called once, and in this + * order, in the UI thread. + */ + @LargeTest + @Feature({"Printing"}) + public void testNormalPrintingFlow() throws Throwable { + if (!ApiCompatibilityUtils.isPrintingSupported()) return; + + final TestShellTab currentTab = launchChromiumTestShellWithUrl(URL).getActiveTab(); + assertTrue(waitForActiveShellToBeDoneLoading()); + + final PrintManagerDelegate mockPrintManagerDelegate = new PrintManagerDelegate() { + @Override + public void print(String printJobName, + PrintDocumentAdapter documentAdapter, + PrintAttributes attributes) { + // Do nothing, as we will emulate the framework call sequence within the test. + } + }; + final PrintingControllerImpl printingController = + (PrintingControllerImpl) PrintingControllerImpl.create(mockPrintManagerDelegate, + new PrintDocumentAdapterWrapper(), PRINT_JOB_NAME); + + startController(printingController, currentTab); + // {@link PrintDocumentAdapter#onStart} is always called first. + callStartOnUiThread(printingController); + + // Create a temporary file to save the PDF. + final File cacheDir = getInstrumentation().getTargetContext().getCacheDir(); + final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTENSION, cacheDir); + final ParcelFileDescriptor fileDescriptor = + ParcelFileDescriptor.open(tempFile, (ParcelFileDescriptor.MODE_CREATE | + ParcelFileDescriptor.MODE_READ_WRITE)); + + PrintAttributes attributes = new PrintAttributes.Builder() + .setMediaSize(PrintAttributes.MediaSize.ISO_A4) + .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) + .setMinMargins(PrintAttributes.Margins.NO_MARGINS) + .build(); + + // Use this to wait for PDF generation to complete, as it will happen asynchronously. + final FutureTask<Boolean> result = + new FutureTask<Boolean>(new Callable<Boolean>() { + @Override + public Boolean call() { + return true; + } + }); + + callLayoutOnUiThread( + printingController, + null, + attributes, + new LayoutResultCallbackWrapperMock() { + // Called on UI thread + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + callWriteOnUiThread(printingController, fileDescriptor, result); + } + }); + + FileInputStream in = null; + try { + // This blocks until the PDF is generated. + result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); + assertTrue(tempFile.length() > 0); + in = new FileInputStream(tempFile); + byte[] b = new byte[PDF_PREAMBLE.length()]; + in.read(b); + String preamble = new String(b); + assertEquals(PDF_PREAMBLE, preamble); + } finally { + callFinishOnUiThread(printingController); + if (in != null) in.close(); + // Close the descriptor, if not closed already. + fileDescriptor.close(); + TestFileUtil.deleteFile(tempFile.getAbsolutePath()); + } + + } + + private void startController(final PrintingControllerImpl controller, final TestShellTab tab) { + try { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + controller.startPrint(new TabPrinter(tab)); + } + }); + } catch (Throwable e) { + fail("Error on calling startPrint of PrintingControllerImpl " + e); + } + } + + private void callStartOnUiThread(final PrintingControllerImpl controller) { + try { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + controller.onStart(); + } + }); + } catch (Throwable e) { + fail("Error on calling onStart of PrintingControllerImpl " + e); + } + } + + private void callLayoutOnUiThread( + final PrintingControllerImpl controller, + final PrintAttributes oldAttributes, + final PrintAttributes newAttributes, + final PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper layoutResultCallback) { + try { + runTestOnUiThread(new Runnable() { + @Override + public void run() { + controller.onLayout( + oldAttributes, + newAttributes, + new CancellationSignal(), + layoutResultCallback, + null); + } + }); + } catch (Throwable e) { + fail("Error on calling onLayout of PrintingControllerImpl " + e); + } + } + + private void callWriteOnUiThread( + final PrintingControllerImpl controller, + final ParcelFileDescriptor descriptor, + final FutureTask<Boolean> result) { + try { + controller.onWrite( + new PageRange[] {PageRange.ALL_PAGES}, + descriptor, + new CancellationSignal(), + new WriteResultCallbackWrapperMock() { + @Override + public void onWriteFinished(PageRange[] pages) { + try { + descriptor.close(); + // Result is ready, signal to continue. + result.run(); + } catch (IOException ex) { + fail("Failed file operation: " + ex.toString()); + } + } + } + ); + } catch (Throwable e) { + fail("Error on calling onWriteInternal of PrintingControllerImpl " + e); + } + } + + private void callFinishOnUiThread(final PrintingControllerImpl controller) { + try { + runTestOnUiThread( new Runnable() { + @Override + public void run() { + controller.onFinish(); + } + }); + } catch (Throwable e) { + fail("Error on calling onFinish of PrintingControllerImpl " + e); + } + } +}
\ No newline at end of file diff --git a/printing/android/java/src/org/chromium/printing/PrintDocumentAdapterWrapper.java b/printing/android/java/src/org/chromium/printing/PrintDocumentAdapterWrapper.java new file mode 100644 index 0000000..1f1f27a --- /dev/null +++ b/printing/android/java/src/org/chromium/printing/PrintDocumentAdapterWrapper.java @@ -0,0 +1,145 @@ +// 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.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.PrintDocumentAdapter.LayoutResultCallback; +import android.print.PrintDocumentAdapter.WriteResultCallback; +import android.print.PrintDocumentInfo; + +/** + * Wrapper for {@link PrintDocumentAdapter} for easier testing. + * + * Normally, {@link PrintDocumentAdapter#LayoutResultCallback} and + * {@link PrintDocumentAdapter#WriteResultCallback} don't have public constructors. This makes + * it impossible to subclass them, which is required to emulate calls to + * {@link PrintDocumentAdapter#onLayout} and {@link PrintDocumentAdapter#onWrite}. This class helps + * bypassing the limitation. + */ +public class PrintDocumentAdapterWrapper extends PrintDocumentAdapter { + private PdfGenerator mPdfGenerator; + + public static interface PdfGenerator { + void onStart(); + void onLayout( + PrintAttributes oldAttributes, + PrintAttributes newAttributes, + CancellationSignal cancellationSignal, + PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper callback, + Bundle metadata); + void onWrite( + final PageRange[] ranges, + final ParcelFileDescriptor destination, + final CancellationSignal cancellationSignal, + final PrintDocumentAdapterWrapper.WriteResultCallbackWrapper callback); + void onFinish(); + } + + public static interface LayoutResultCallbackWrapper { + void onLayoutFinished(PrintDocumentInfo info, boolean changed); + void onLayoutFailed(CharSequence error); + void onLayoutCancelled(); + } + + public static interface WriteResultCallbackWrapper { + void onWriteFinished(PageRange[] pages); + void onWriteFailed(CharSequence error); + void onWriteCancelled(); + } + + public static class LayoutResultCallbackWrapperImpl implements LayoutResultCallbackWrapper { + private LayoutResultCallback mCallback = null; + public LayoutResultCallbackWrapperImpl(LayoutResultCallback callback) { + assert callback != null; + mCallback = callback; + } + + @Override + public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { + mCallback.onLayoutFinished(info, changed); + } + + @Override + public void onLayoutFailed(CharSequence error) { + mCallback.onLayoutFailed(error); + } + + @Override + public void onLayoutCancelled() { + mCallback.onLayoutCancelled(); + } + } + + public static class WriteResultCallbackWrapperImpl implements WriteResultCallbackWrapper { + private WriteResultCallback mCallback = null; + public WriteResultCallbackWrapperImpl(WriteResultCallback callback) { + assert callback != null; + mCallback = callback; + } + + @Override + public void onWriteFinished(PageRange[] pages) { + mCallback.onWriteFinished(pages); + } + + @Override + public void onWriteFailed(CharSequence error) { + mCallback.onWriteFailed(error); + } + + @Override + public void onWriteCancelled() { + mCallback.onWriteCancelled(); + } + } + + public void setPdfGenerator(PdfGenerator pdfGenerator) { + mPdfGenerator = pdfGenerator; + } + + /** + * Initiates the printing process within the framework + */ + public void print(PrintManagerDelegate printManager, String title) { + printManager.print(title, this, null); + } + + @Override + public void onStart() { + mPdfGenerator.onStart(); + } + + @Override + public void onLayout( + PrintAttributes oldAttributes, + PrintAttributes newAttributes, + CancellationSignal cancellationSignal, + LayoutResultCallback callback, + Bundle metadata) { + mPdfGenerator.onLayout(oldAttributes, newAttributes, cancellationSignal, + new LayoutResultCallbackWrapperImpl(callback), metadata); + } + + @Override + public void onWrite( + final PageRange[] ranges, + final ParcelFileDescriptor destination, + final CancellationSignal cancellationSignal, + final WriteResultCallback callback) { + mPdfGenerator.onWrite(ranges, destination, cancellationSignal, + new WriteResultCallbackWrapperImpl(callback)); + } + + @Override + public void onFinish() { + super.onFinish(); + mPdfGenerator.onFinish(); + } +}
\ No newline at end of file diff --git a/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java b/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java index 239cff0..3ef2e7b 100644 --- a/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java +++ b/printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java @@ -5,6 +5,7 @@ package org.chromium.printing; import org.chromium.base.ThreadUtils; +import org.chromium.printing.PrintDocumentAdapterWrapper.PdfGenerator;; import android.os.Bundle; import android.os.CancellationSignal; @@ -14,8 +15,6 @@ 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 java.io.IOException; @@ -30,7 +29,7 @@ import java.util.Iterator; * print button. The singleton object lives in UI thread. Interaction with the native side is * carried through PrintingContext class. */ -public class PrintingControllerImpl extends PrintDocumentAdapter implements PrintingController { +public class PrintingControllerImpl implements PrintingController, PdfGenerator { private static final String LOG_TAG = "PrintingControllerImpl"; @@ -66,7 +65,7 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin private int[] mPages; /** The callback function to inform the result of PDF generation to the framework. */ - private PrintDocumentAdapter.WriteResultCallback mOnWriteCallback; + private PrintDocumentAdapterWrapper.WriteResultCallbackWrapper mOnWriteCallback; /** * The callback function to inform the result of layout to the framework. We save the callback @@ -74,11 +73,14 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin * number of expected pages back to the framework through this callback once the native side * has that information. */ - private PrintDocumentAdapter.LayoutResultCallback mOnLayoutCallback; + private PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper mOnLayoutCallback; /** The object through which native PDF generation process is initiated. */ private Printable mPrintable; + /** The object through which the framework will make calls for generating PDF. */ + private PrintDocumentAdapterWrapper mPrintDocumentAdapterWrapper; + private int mPrintingState = PRINTING_STATE_READY; /** Whether layouting parameters have been changed to require a new PDF generation. */ @@ -87,9 +89,13 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin /** Total number of pages to print with initial print dialog settings. */ private int mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; - private PrintingControllerImpl(PrintManagerDelegate printManager, String errorText) { + private PrintingControllerImpl(PrintManagerDelegate printManager, + PrintDocumentAdapterWrapper printDocumentAdapterWrapper, + String errorText) { mPrintManager = printManager; mErrorMessage = errorText; + mPrintDocumentAdapterWrapper = printDocumentAdapterWrapper; + mPrintDocumentAdapterWrapper.setPdfGenerator(this); } /** @@ -97,17 +103,20 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin * * The controller is a singleton, since there can be only one printing action at any time. * + * @param printDocumentAdapterWrapper The object through which the framework will make calls + * for generating PDF. * @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 so src/printing/android * doesn't need any string dependency. * @return The resulting PrintingController. */ public static PrintingController create(PrintManagerDelegate printManager, - String errorText) { + PrintDocumentAdapterWrapper printDocumentAdapterWrapper, String errorText) { ThreadUtils.assertOnUiThread(); if (sInstance == null) { - sInstance = new PrintingControllerImpl(printManager, errorText); + sInstance = new PrintingControllerImpl(printManager, + printDocumentAdapterWrapper, errorText); } return sInstance; } @@ -162,7 +171,7 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin @Override public void startPrint(final Printable printable) { mPrintable = printable; - mPrintManager.print(printable.getTitle(), this, null); + mPrintDocumentAdapterWrapper.print(mPrintManager, printable.getTitle()); } @Override @@ -186,9 +195,12 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin } @Override - public void onLayout(PrintAttributes oldAttributes, - PrintAttributes newAttributes, CancellationSignal cancellationSignal, - LayoutResultCallback callback, Bundle metadata) { + public void onLayout( + PrintAttributes oldAttributes, + PrintAttributes newAttributes, + CancellationSignal cancellationSignal, + PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper 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(); @@ -248,7 +260,7 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin final PageRange[] ranges, final ParcelFileDescriptor destination, final CancellationSignal cancellationSignal, - final WriteResultCallback callback) { + final PrintDocumentAdapterWrapper.WriteResultCallbackWrapper callback) { if (mPrintingContext == null) { callback.onWriteFailed(mErrorMessage); resetCallbacks(); @@ -290,7 +302,6 @@ public class PrintingControllerImpl extends PrintDocumentAdapter implements Prin @Override public void onFinish() { - super.onFinish(); mLastKnownMaxPages = PrintDocumentInfo.PAGE_COUNT_UNKNOWN; mPages = null; |