1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.print.cts; 18 19 import static org.mockito.Matchers.any; 20 import static org.mockito.Matchers.argThat; 21 import static org.mockito.Matchers.eq; 22 import static org.mockito.Mockito.doAnswer; 23 import static org.mockito.Mockito.doCallRealMethod; 24 import static org.mockito.Mockito.mock; 25 import static org.mockito.Mockito.when; 26 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.content.res.Resources; 30 import android.graphics.pdf.PdfDocument; 31 import android.os.Bundle; 32 import android.os.CancellationSignal; 33 import android.os.ParcelFileDescriptor; 34 import android.os.SystemClock; 35 import android.print.PageRange; 36 import android.print.PrintAttributes; 37 import android.print.PrintDocumentAdapter; 38 import android.print.PrintDocumentAdapter.LayoutResultCallback; 39 import android.print.PrintDocumentAdapter.WriteResultCallback; 40 import android.print.PrintManager; 41 import android.print.PrinterId; 42 import android.print.cts.services.FirstPrintService; 43 import android.print.cts.services.PrintServiceCallbacks; 44 import android.print.cts.services.PrinterDiscoverySessionCallbacks; 45 import android.print.cts.services.SecondPrintService; 46 import android.print.cts.services.StubbablePrinterDiscoverySession; 47 import android.print.pdf.PrintedPdfDocument; 48 import android.printservice.PrintJob; 49 import android.printservice.PrintService; 50 import android.support.test.uiautomator.UiAutomatorTestCase; 51 import android.support.test.uiautomator.UiObject; 52 import android.support.test.uiautomator.UiObjectNotFoundException; 53 import android.support.test.uiautomator.UiSelector; 54 import android.util.DisplayMetrics; 55 56 import org.hamcrest.BaseMatcher; 57 import org.hamcrest.Description; 58 import org.mockito.InOrder; 59 import org.mockito.stubbing.Answer; 60 61 import java.io.File; 62 import java.io.FileOutputStream; 63 import java.io.IOException; 64 import java.util.List; 65 import java.util.Locale; 66 import java.util.concurrent.TimeoutException; 67 68 /** 69 * This is the base class for print tests. 70 */ 71 public abstract class BasePrintTest extends UiAutomatorTestCase { 72 73 private static final long OPERATION_TIMEOUT = 100000000; 74 75 private static final String ARG_PRIVILEGED_OPS = "ARG_PRIVILEGED_OPS"; 76 77 private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; 78 79 protected static final String PRINT_JOB_NAME = "Test"; 80 81 private PrintDocumentActivity mActivity; 82 83 private Locale mOldLocale; 84 85 private CallCounter mCancelOperationCounter; 86 private CallCounter mLayoutCallCounter; 87 private CallCounter mWriteCallCounter; 88 private CallCounter mFinishCallCounter; 89 private CallCounter mPrintJobQueuedCallCounter; 90 private CallCounter mDestroySessionCallCounter; 91 92 @Override setUp()93 public void setUp() throws Exception { 94 // Make sure we start with a clean slate. 95 clearPrintSpoolerData(); 96 97 // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2 98 // Dexmaker is used by mockito. 99 System.setProperty("dexmaker.dexcache", getInstrumentation() 100 .getTargetContext().getCacheDir().getPath()); 101 102 // Set to US locale. 103 Resources resources = getInstrumentation().getTargetContext().getResources(); 104 Configuration oldConfiguration = resources.getConfiguration(); 105 if (!oldConfiguration.locale.equals(Locale.US)) { 106 mOldLocale = oldConfiguration.locale; 107 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 108 Configuration newConfiguration = new Configuration(oldConfiguration); 109 newConfiguration.locale = Locale.US; 110 resources.updateConfiguration(newConfiguration, displayMetrics); 111 } 112 113 // Initialize the latches. 114 mCancelOperationCounter = new CallCounter(); 115 mLayoutCallCounter = new CallCounter(); 116 mFinishCallCounter = new CallCounter(); 117 mWriteCallCounter = new CallCounter(); 118 mFinishCallCounter = new CallCounter(); 119 mPrintJobQueuedCallCounter = new CallCounter(); 120 mDestroySessionCallCounter = new CallCounter(); 121 122 // Create the activity for the right locale. 123 createActivity(); 124 } 125 126 @Override tearDown()127 public void tearDown() throws Exception { 128 // Done with the activity. 129 getActivity().finish(); 130 131 // Restore the locale if needed. 132 if (mOldLocale != null) { 133 Resources resources = getInstrumentation().getTargetContext().getResources(); 134 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 135 Configuration newConfiguration = new Configuration(resources.getConfiguration()); 136 newConfiguration.locale = mOldLocale; 137 mOldLocale = null; 138 resources.updateConfiguration(newConfiguration, displayMetrics); 139 } 140 141 // Make sure the spooler is cleaned. 142 clearPrintSpoolerData(); 143 } 144 print(final PrintDocumentAdapter adapter)145 protected void print(final PrintDocumentAdapter adapter) { 146 // Initiate printing as if coming from the app. 147 getInstrumentation().runOnMainSync(new Runnable() { 148 @Override 149 public void run() { 150 PrintManager printManager = (PrintManager) getActivity() 151 .getSystemService(Context.PRINT_SERVICE); 152 printManager.print("Print job", adapter, null); 153 } 154 }); 155 } 156 onCancelOperationCalled()157 protected void onCancelOperationCalled() { 158 mCancelOperationCounter.call(); 159 } 160 onLayoutCalled()161 protected void onLayoutCalled() { 162 mLayoutCallCounter.call(); 163 } 164 getWriteCallCount()165 protected int getWriteCallCount() { 166 return mWriteCallCounter.getCallCount(); 167 } 168 onWriteCalled()169 protected void onWriteCalled() { 170 mWriteCallCounter.call(); 171 } 172 onFinishCalled()173 protected void onFinishCalled() { 174 mFinishCallCounter.call(); 175 } 176 onPrintJobQueuedCalled()177 protected void onPrintJobQueuedCalled() { 178 mPrintJobQueuedCallCounter.call(); 179 } 180 onPrinterDiscoverySessionDestroyCalled()181 protected void onPrinterDiscoverySessionDestroyCalled() { 182 mDestroySessionCallCounter.call(); 183 } 184 waitForCancelOperationCallbackCalled()185 protected void waitForCancelOperationCallbackCalled() { 186 waitForCallbackCallCount(mCancelOperationCounter, 1, 187 "Did not get expected call to onCancel for the current operation."); 188 } 189 waitForPrinterDiscoverySessionDestroyCallbackCalled()190 protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() { 191 waitForCallbackCallCount(mDestroySessionCallCounter, 1, 192 "Did not get expected call to onDestroyPrinterDiscoverySession."); 193 } 194 waitForServiceOnPrintJobQueuedCallbackCalled()195 protected void waitForServiceOnPrintJobQueuedCallbackCalled() { 196 waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1, 197 "Did not get expected call to onPrintJobQueued."); 198 } 199 waitForAdapterFinishCallbackCalled()200 protected void waitForAdapterFinishCallbackCalled() { 201 waitForCallbackCallCount(mFinishCallCounter, 1, 202 "Did not get expected call to finish."); 203 } 204 waitForLayoutAdapterCallbackCount(int count)205 protected void waitForLayoutAdapterCallbackCount(int count) { 206 waitForCallbackCallCount(mLayoutCallCounter, count, 207 "Did not get expected call to layout."); 208 } 209 waitForWriteAdapterCallback()210 protected void waitForWriteAdapterCallback() { 211 waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write."); 212 } 213 waitForCallbackCallCount(CallCounter counter, int count, String message)214 private void waitForCallbackCallCount(CallCounter counter, int count, String message) { 215 try { 216 counter.waitForCount(count, OPERATION_TIMEOUT); 217 } catch (TimeoutException te) { 218 fail(message); 219 } 220 } 221 selectPrinter(String printerName)222 protected void selectPrinter(String printerName) throws UiObjectNotFoundException { 223 try { 224 UiObject destinationSpinner = new UiObject(new UiSelector().resourceId( 225 "com.android.printspooler:id/destination_spinner")); 226 destinationSpinner.click(); 227 UiObject printerOption = new UiObject(new UiSelector().text(printerName)); 228 printerOption.click(); 229 } catch (UiObjectNotFoundException e) { 230 dumpWindowHierarchy(); 231 throw new UiObjectNotFoundException(e); 232 } 233 } 234 changeOrientation(String orientation)235 protected void changeOrientation(String orientation) throws UiObjectNotFoundException { 236 try { 237 UiObject orientationSpinner = new UiObject(new UiSelector().resourceId( 238 "com.android.printspooler:id/orientation_spinner")); 239 orientationSpinner.click(); 240 UiObject orientationOption = new UiObject(new UiSelector().text(orientation)); 241 orientationOption.click(); 242 } catch (UiObjectNotFoundException e) { 243 dumpWindowHierarchy(); 244 throw new UiObjectNotFoundException(e); 245 } 246 } 247 changeMediaSize(String mediaSize)248 protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException { 249 try { 250 UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId( 251 "com.android.printspooler:id/paper_size_spinner")); 252 mediaSizeSpinner.click(); 253 UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize)); 254 mediaSizeOption.click(); 255 } catch (UiObjectNotFoundException e) { 256 dumpWindowHierarchy(); 257 throw new UiObjectNotFoundException(e); 258 } 259 } 260 changeColor(String color)261 protected void changeColor(String color) throws UiObjectNotFoundException { 262 try { 263 UiObject colorSpinner = new UiObject(new UiSelector().resourceId( 264 "com.android.printspooler:id/color_spinner")); 265 colorSpinner.click(); 266 UiObject colorOption = new UiObject(new UiSelector().text(color)); 267 colorOption.click(); 268 } catch (UiObjectNotFoundException e) { 269 dumpWindowHierarchy(); 270 throw new UiObjectNotFoundException(e); 271 } 272 } 273 clickPrintButton()274 protected void clickPrintButton() throws UiObjectNotFoundException { 275 try { 276 UiObject printButton = new UiObject(new UiSelector().resourceId( 277 "com.android.printspooler:id/print_button")); 278 printButton.click(); 279 } catch (UiObjectNotFoundException e) { 280 dumpWindowHierarchy(); 281 throw new UiObjectNotFoundException(e); 282 } 283 } 284 dumpWindowHierarchy()285 private void dumpWindowHierarchy() { 286 String name = "print-test-failure-" + System.currentTimeMillis() + ".xml"; 287 File file = new File(getActivity().getFilesDir(), name); 288 getUiDevice().dumpWindowHierarchy(file.toString()); 289 } 290 getActivity()291 protected PrintDocumentActivity getActivity() { 292 return mActivity; 293 } 294 createActivity()295 private void createActivity() { 296 mActivity = launchActivity( 297 getInstrumentation().getTargetContext().getPackageName(), 298 PrintDocumentActivity.class, null); 299 } 300 openPrintOptions()301 protected void openPrintOptions() throws UiObjectNotFoundException { 302 UiObject expandHandle = new UiObject(new UiSelector().resourceId( 303 "com.android.printspooler:id/expand_collapse_handle")); 304 expandHandle.click(); 305 } 306 clearPrintSpoolerData()307 protected void clearPrintSpoolerData() throws Exception { 308 IPrivilegedOperations privilegedOps = IPrivilegedOperations.Stub.asInterface( 309 getParams().getBinder(ARG_PRIVILEGED_OPS)); 310 privilegedOps.clearApplicationUserData(PRINT_SPOOLER_PACKAGE_NAME); 311 } 312 verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)313 protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, 314 PrintAttributes oldAttributes, PrintAttributes newAttributes, 315 final boolean forPreview) { 316 inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes), 317 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat( 318 new BaseMatcher<Bundle>() { 319 @Override 320 public boolean matches(Object item) { 321 Bundle bundle = (Bundle) item; 322 return forPreview == bundle.getBoolean( 323 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW); 324 } 325 326 @Override 327 public void describeTo(Description description) { 328 /* do nothing */ 329 } 330 })); 331 } 332 createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)333 protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, 334 Answer<Void> writeAnswer, Answer<Void> finishAnswer) { 335 // Create a mock print adapter. 336 PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class); 337 if (layoutAnswer != null) { 338 doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class), 339 any(PrintAttributes.class), any(CancellationSignal.class), 340 any(LayoutResultCallback.class), any(Bundle.class)); 341 } 342 if (writeAnswer != null) { 343 doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class), 344 any(ParcelFileDescriptor.class), any(CancellationSignal.class), 345 any(WriteResultCallback.class)); 346 } 347 if (finishAnswer != null) { 348 doAnswer(finishAnswer).when(adapter).onFinish(); 349 } 350 return adapter; 351 } 352 353 @SuppressWarnings("unchecked") createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)354 protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 355 Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, 356 Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, 357 Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) { 358 PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class); 359 360 doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class)); 361 when(callbacks.getSession()).thenCallRealMethod(); 362 363 if (onStartPrinterDiscovery != null) { 364 doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery( 365 any(List.class)); 366 } 367 if (onStopPrinterDiscovery != null) { 368 doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery(); 369 } 370 if (onValidatePrinters != null) { 371 doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters( 372 any(List.class)); 373 } 374 if (onStartPrinterStateTracking != null) { 375 doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking( 376 any(PrinterId.class)); 377 } 378 if (onStopPrinterStateTracking != null) { 379 doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking( 380 any(PrinterId.class)); 381 } 382 if (onDestroy != null) { 383 doAnswer(onDestroy).when(callbacks).onDestroy(); 384 } 385 386 return callbacks; 387 } 388 createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)389 protected PrintServiceCallbacks createMockPrintServiceCallbacks( 390 Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, 391 Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) { 392 final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class); 393 394 doCallRealMethod().when(service).setService(any(PrintService.class)); 395 when(service.getService()).thenCallRealMethod(); 396 397 if (onCreatePrinterDiscoverySessionCallbacks != null) { 398 doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service) 399 .onCreatePrinterDiscoverySessionCallbacks(); 400 } 401 if (onPrintJobQueued != null) { 402 doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class)); 403 } 404 if (onRequestCancelPrintJob != null) { 405 doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob( 406 any(PrintJob.class)); 407 } 408 409 return service; 410 } 411 writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)412 protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, 413 int fromIndex, int toIndex) throws IOException { 414 PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints); 415 final int pageCount = toIndex - fromIndex + 1; 416 for (int i = 0; i < pageCount; i++) { 417 PdfDocument.Page page = document.startPage(i); 418 document.finishPage(page); 419 } 420 FileOutputStream fos = new FileOutputStream(output.getFileDescriptor()); 421 document.writeTo(fos); 422 document.close(); 423 } 424 425 protected final class CallCounter { 426 private final Object mLock = new Object(); 427 428 private int mCallCount; 429 call()430 public void call() { 431 synchronized (mLock) { 432 mCallCount++; 433 mLock.notifyAll(); 434 } 435 } 436 getCallCount()437 public int getCallCount() { 438 synchronized (mLock) { 439 return mCallCount; 440 } 441 } 442 waitForCount(int count, long timeoutMillis)443 public void waitForCount(int count, long timeoutMillis) throws TimeoutException { 444 synchronized (mLock) { 445 final long startTimeMillis = SystemClock.uptimeMillis(); 446 while (mCallCount < count) { 447 try { 448 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 449 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 450 if (remainingTimeMillis <= 0) { 451 throw new TimeoutException(); 452 } 453 mLock.wait(timeoutMillis); 454 } catch (InterruptedException ie) { 455 /* ignore */ 456 } 457 } 458 } 459 } 460 } 461 } 462