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. 4 5package org.chromium.printing; 6 7import android.annotation.TargetApi; 8import android.os.Build; 9import android.os.CancellationSignal; 10import android.os.ParcelFileDescriptor; 11import android.print.PageRange; 12import android.print.PrintAttributes; 13import android.print.PrintDocumentAdapter; 14import android.print.PrintDocumentInfo; 15import android.test.suitebuilder.annotation.LargeTest; 16 17import org.chromium.base.ApiCompatibilityUtils; 18import org.chromium.base.test.util.Feature; 19import org.chromium.base.test.util.TestFileUtil; 20import org.chromium.base.test.util.UrlUtils; 21import org.chromium.chrome.browser.printing.TabPrinter; 22import org.chromium.chrome.shell.ChromeShellTab; 23import org.chromium.chrome.shell.ChromeShellTestBase; 24 25import java.io.File; 26import java.io.FileInputStream; 27import java.io.IOException; 28import java.util.concurrent.Callable; 29import java.util.concurrent.FutureTask; 30import java.util.concurrent.TimeUnit; 31 32/** 33 * Tests Android printing. 34 * TODO(cimamoglu): Add a test with cancellation. 35 * TODO(cimamoglu): Add a test with multiple, stacked onLayout/onWrite calls. 36 * TODO(cimamoglu): Add a test which emulates Chromium failing to generate a PDF. 37 */ 38public class PrintingControllerTest extends ChromeShellTestBase { 39 40 private static final String TEMP_FILE_NAME = "temp_print"; 41 private static final String TEMP_FILE_EXTENSION = ".pdf"; 42 private static final String PRINT_JOB_NAME = "foo"; 43 private static final String URL = UrlUtils.encodeHtmlDataUri( 44 "<html><head></head><body>foo</body></html>"); 45 private static final String PDF_PREAMBLE = "%PDF-1"; 46 private static final long TEST_TIMEOUT = 20000L; 47 48 private static class LayoutResultCallbackWrapperMock implements 49 PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper { 50 @Override 51 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) {} 52 53 @Override 54 public void onLayoutFailed(CharSequence error) {} 55 56 @Override 57 public void onLayoutCancelled() {} 58 } 59 60 private static class WriteResultCallbackWrapperMock implements 61 PrintDocumentAdapterWrapper.WriteResultCallbackWrapper { 62 @Override 63 public void onWriteFinished(PageRange[] pages) {} 64 65 @Override 66 public void onWriteFailed(CharSequence error) {} 67 68 @Override 69 public void onWriteCancelled() {} 70 } 71 72 /** 73 * Test a basic printing flow by emulating the corresponding system calls to the printing 74 * controller: onStart, onLayout, onWrite, onFinish. Each one is called once, and in this 75 * order, in the UI thread. 76 */ 77 @TargetApi(Build.VERSION_CODES.KITKAT) 78 @LargeTest 79 @Feature({"Printing"}) 80 public void testNormalPrintingFlow() throws Throwable { 81 if (!ApiCompatibilityUtils.isPrintingSupported()) return; 82 83 final ChromeShellTab currentTab = launchChromeShellWithUrl(URL).getActiveTab(); 84 assertTrue(waitForActiveShellToBeDoneLoading()); 85 86 final PrintingControllerImpl printingController = createControllerOnUiThread(); 87 88 startControllerOnUiThread(printingController, currentTab); 89 // {@link PrintDocumentAdapter#onStart} is always called first. 90 callStartOnUiThread(printingController); 91 92 // Create a temporary file to save the PDF. 93 final File cacheDir = getInstrumentation().getTargetContext().getCacheDir(); 94 final File tempFile = File.createTempFile(TEMP_FILE_NAME, TEMP_FILE_EXTENSION, cacheDir); 95 final ParcelFileDescriptor fileDescriptor = 96 ParcelFileDescriptor.open(tempFile, (ParcelFileDescriptor.MODE_CREATE | 97 ParcelFileDescriptor.MODE_READ_WRITE)); 98 99 PrintAttributes attributes = new PrintAttributes.Builder() 100 .setMediaSize(PrintAttributes.MediaSize.ISO_A4) 101 .setResolution(new PrintAttributes.Resolution("foo", "bar", 300, 300)) 102 .setMinMargins(PrintAttributes.Margins.NO_MARGINS) 103 .build(); 104 105 // Use this to wait for PDF generation to complete, as it will happen asynchronously. 106 final FutureTask<Boolean> result = 107 new FutureTask<Boolean>(new Callable<Boolean>() { 108 @Override 109 public Boolean call() { 110 return true; 111 } 112 }); 113 114 callLayoutOnUiThread( 115 printingController, 116 null, 117 attributes, 118 new LayoutResultCallbackWrapperMock() { 119 // Called on UI thread 120 @Override 121 public void onLayoutFinished(PrintDocumentInfo info, boolean changed) { 122 callWriteOnUiThread(printingController, fileDescriptor, result); 123 } 124 }); 125 126 FileInputStream in = null; 127 try { 128 // This blocks until the PDF is generated. 129 result.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 130 assertTrue(tempFile.length() > 0); 131 in = new FileInputStream(tempFile); 132 byte[] b = new byte[PDF_PREAMBLE.length()]; 133 in.read(b); 134 String preamble = new String(b); 135 assertEquals(PDF_PREAMBLE, preamble); 136 } finally { 137 callFinishOnUiThread(printingController); 138 if (in != null) in.close(); 139 // Close the descriptor, if not closed already. 140 fileDescriptor.close(); 141 TestFileUtil.deleteFile(tempFile.getAbsolutePath()); 142 } 143 144 } 145 146 private PrintingControllerImpl createControllerOnUiThread() { 147 try { 148 final FutureTask<PrintingControllerImpl> task = 149 new FutureTask<PrintingControllerImpl>(new Callable<PrintingControllerImpl>() { 150 @Override 151 public PrintingControllerImpl call() throws Exception { 152 return (PrintingControllerImpl) PrintingControllerImpl.create( 153 new PrintDocumentAdapterWrapper(), 154 PRINT_JOB_NAME); 155 } 156 }); 157 158 runTestOnUiThread(task); 159 PrintingControllerImpl result = task.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS); 160 return result; 161 } catch (Throwable e) { 162 fail("Error on creating PrintingControllerImpl on the UI thread: " + e); 163 } 164 return null; 165 } 166 167 private void startControllerOnUiThread(final PrintingControllerImpl controller, 168 final ChromeShellTab tab) { 169 try { 170 final PrintManagerDelegate mockPrintManagerDelegate = new PrintManagerDelegate() { 171 @Override 172 public void print(String printJobName, 173 PrintDocumentAdapter documentAdapter, 174 PrintAttributes attributes) { 175 // Do nothing, as we will emulate the framework call sequence within the test. 176 } 177 }; 178 179 runTestOnUiThread(new Runnable() { 180 @Override 181 public void run() { 182 controller.startPrint(new TabPrinter(tab), mockPrintManagerDelegate); 183 } 184 }); 185 } catch (Throwable e) { 186 fail("Error on calling startPrint of PrintingControllerImpl " + e); 187 } 188 } 189 190 private void callStartOnUiThread(final PrintingControllerImpl controller) { 191 try { 192 runTestOnUiThread(new Runnable() { 193 @Override 194 public void run() { 195 controller.onStart(); 196 } 197 }); 198 } catch (Throwable e) { 199 fail("Error on calling onStart of PrintingControllerImpl " + e); 200 } 201 } 202 203 @TargetApi(Build.VERSION_CODES.JELLY_BEAN) 204 private void callLayoutOnUiThread( 205 final PrintingControllerImpl controller, 206 final PrintAttributes oldAttributes, 207 final PrintAttributes newAttributes, 208 final PrintDocumentAdapterWrapper.LayoutResultCallbackWrapper layoutResultCallback) { 209 try { 210 runTestOnUiThread(new Runnable() { 211 @Override 212 public void run() { 213 controller.onLayout( 214 oldAttributes, 215 newAttributes, 216 new CancellationSignal(), 217 layoutResultCallback, 218 null); 219 } 220 }); 221 } catch (Throwable e) { 222 fail("Error on calling onLayout of PrintingControllerImpl " + e); 223 } 224 } 225 226 @TargetApi(Build.VERSION_CODES.KITKAT) 227 private void callWriteOnUiThread( 228 final PrintingControllerImpl controller, 229 final ParcelFileDescriptor descriptor, 230 final FutureTask<Boolean> result) { 231 try { 232 controller.onWrite( 233 new PageRange[] {PageRange.ALL_PAGES}, 234 descriptor, 235 new CancellationSignal(), 236 new WriteResultCallbackWrapperMock() { 237 @Override 238 public void onWriteFinished(PageRange[] pages) { 239 try { 240 descriptor.close(); 241 // Result is ready, signal to continue. 242 result.run(); 243 } catch (IOException ex) { 244 fail("Failed file operation: " + ex.toString()); 245 } 246 } 247 } 248 ); 249 } catch (Throwable e) { 250 fail("Error on calling onWriteInternal of PrintingControllerImpl " + e); 251 } 252 } 253 254 private void callFinishOnUiThread(final PrintingControllerImpl controller) { 255 try { 256 runTestOnUiThread(new Runnable() { 257 @Override 258 public void run() { 259 controller.onFinish(); 260 } 261 }); 262 } catch (Throwable e) { 263 fail("Error on calling onFinish of PrintingControllerImpl " + e); 264 } 265 } 266} 267