blob: 8c906c6f48babfe7080ff1d768fc7ff4b7e41e47 [file] [log] [blame]
// 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.chrome.browser.printing;
import android.annotation.TargetApi;
import android.os.Build;
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 android.test.suitebuilder.annotation.LargeTest;
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.shell.ChromeShellTab;
import org.chromium.chrome.shell.ChromeShellTestBase;
import org.chromium.printing.PrintDocumentAdapterWrapper;
import org.chromium.printing.PrintManagerDelegate;
import org.chromium.printing.PrintingControllerImpl;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
/**
* 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 ChromeShellTestBase {
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 final 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.
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
@LargeTest
@Feature({"Printing"})
public void testNormalPrintingFlow() throws Throwable {
if (!ApiCompatibilityUtils.isPrintingSupported()) return;
final ChromeShellTab currentTab = launchChromeShellWithUrl(URL).getActiveTab();
assertTrue(waitForActiveShellToBeDoneLoading());
final PrintingControllerImpl printingController = createControllerOnUiThread();
startControllerOnUiThread(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 PrintingControllerImpl createControllerOnUiThread() {
try {
final FutureTask<PrintingControllerImpl> task =
new FutureTask<PrintingControllerImpl>(new Callable<PrintingControllerImpl>() {
@Override
public PrintingControllerImpl call() throws Exception {
return (PrintingControllerImpl) PrintingControllerImpl.create(
new PrintDocumentAdapterWrapper(),
PRINT_JOB_NAME);
}
});
runTestOnUiThread(task);
PrintingControllerImpl result = task.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
return result;
} catch (Throwable e) {
fail("Error on creating PrintingControllerImpl on the UI thread: " + e);
}
return null;
}
private void startControllerOnUiThread(final PrintingControllerImpl controller,
final ChromeShellTab tab) {
try {
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.
}
};
runTestOnUiThread(new Runnable() {
@Override
public void run() {
controller.startPrint(new TabPrinter(tab), mockPrintManagerDelegate);
}
});
} 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);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
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);
}
}
@TargetApi(Build.VERSION_CODES.KITKAT)
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);
}
}
}