summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-04 21:28:54 +0000
committercimamoglu@chromium.org <cimamoglu@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-04 21:28:54 +0000
commit334c6d06ca072128348ee8e0f39a687b8ac14b5e (patch)
tree5224785514b7475bae66f84a39e3ee209041fa84
parentcc5e8f152817ca6102d0f15d5c3a5452e04553f1 (diff)
downloadchromium_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
-rw-r--r--chrome/android/java/src/org/chromium/chrome/browser/printing/PrintingControllerFactory.java5
-rw-r--r--chrome/android/javatests/src/org/chromium/chrome/browser/printing/PrintingControllerTest.java245
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintDocumentAdapterWrapper.java145
-rw-r--r--printing/android/java/src/org/chromium/printing/PrintingControllerImpl.java39
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;