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.pm.PackageManager; 29 import android.content.res.Configuration; 30 import android.content.res.Resources; 31 import android.cts.util.SystemUtil; 32 import android.graphics.pdf.PdfDocument; 33 import android.os.Bundle; 34 import android.os.CancellationSignal; 35 import android.os.ParcelFileDescriptor; 36 import android.os.SystemClock; 37 import android.print.PageRange; 38 import android.print.PrintAttributes; 39 import android.print.PrintDocumentAdapter; 40 import android.print.PrintDocumentAdapter.LayoutResultCallback; 41 import android.print.PrintDocumentAdapter.WriteResultCallback; 42 import android.print.PrintManager; 43 import android.print.PrinterId; 44 import android.print.cts.services.FirstPrintService; 45 import android.print.cts.services.PrintServiceCallbacks; 46 import android.print.cts.services.PrinterDiscoverySessionCallbacks; 47 import android.print.cts.services.SecondPrintService; 48 import android.print.cts.services.StubbablePrinterDiscoverySession; 49 import android.print.pdf.PrintedPdfDocument; 50 import android.printservice.PrintJob; 51 import android.printservice.PrintService; 52 import android.support.test.uiautomator.UiAutomatorTestCase; 53 import android.support.test.uiautomator.UiObject; 54 import android.support.test.uiautomator.UiObjectNotFoundException; 55 import android.support.test.uiautomator.UiSelector; 56 import android.util.DisplayMetrics; 57 58 import org.hamcrest.BaseMatcher; 59 import org.hamcrest.Description; 60 import org.mockito.InOrder; 61 import org.mockito.stubbing.Answer; 62 63 import java.io.BufferedReader; 64 import java.io.File; 65 import java.io.FileInputStream; 66 import java.io.FileOutputStream; 67 import java.io.IOException; 68 import java.io.InputStreamReader; 69 import java.util.ArrayList; 70 import java.util.List; 71 import java.util.Locale; 72 import java.util.concurrent.TimeoutException; 73 74 /** 75 * This is the base class for print tests. 76 */ 77 public abstract class BasePrintTest extends UiAutomatorTestCase { 78 79 private static final long OPERATION_TIMEOUT = 100000000; 80 81 private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; 82 83 protected static final String PRINT_JOB_NAME = "Test"; 84 85 private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success"; 86 87 private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s"; 88 89 private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable "; 90 91 private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable "; 92 93 private PrintDocumentActivity mActivity; 94 95 private Locale mOldLocale; 96 97 private CallCounter mCancelOperationCounter; 98 private CallCounter mLayoutCallCounter; 99 private CallCounter mWriteCallCounter; 100 private CallCounter mFinishCallCounter; 101 private CallCounter mPrintJobQueuedCallCounter; 102 private CallCounter mDestroySessionCallCounter; 103 104 private String[] mEnabledImes; 105 getEnabledImes()106 private String[] getEnabledImes() throws IOException { 107 List<String> imeList = new ArrayList<>(); 108 109 ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation() 110 .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS); 111 BufferedReader reader = new BufferedReader( 112 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor()))); 113 114 String line; 115 while ((line = reader.readLine()) != null) { 116 imeList.add(line); 117 } 118 119 String[] imeArray = new String[imeList.size()]; 120 imeList.toArray(imeArray); 121 122 return imeArray; 123 } 124 disableImes()125 private void disableImes() throws Exception { 126 mEnabledImes = getEnabledImes(); 127 for (String ime : mEnabledImes) { 128 String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime; 129 SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand); 130 } 131 } 132 enableImes()133 private void enableImes() throws Exception { 134 for (String ime : mEnabledImes) { 135 String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime; 136 SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand); 137 } 138 mEnabledImes = null; 139 } 140 141 @Override runTest()142 protected void runTest() throws Throwable { 143 // Do nothing if the device does not support printing. 144 if (supportsPrinting()) { 145 super.runTest(); 146 } 147 } 148 149 @Override setUp()150 public void setUp() throws Exception { 151 super.setUp(); 152 if (!supportsPrinting()) { 153 return; 154 } 155 156 // Make sure we start with a clean slate. 157 clearPrintSpoolerData(); 158 enablePrintServices(); 159 disableImes(); 160 161 // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2 162 // Dexmaker is used by mockito. 163 System.setProperty("dexmaker.dexcache", getInstrumentation() 164 .getTargetContext().getCacheDir().getPath()); 165 166 // Set to US locale. 167 Resources resources = getInstrumentation().getTargetContext().getResources(); 168 Configuration oldConfiguration = resources.getConfiguration(); 169 if (!oldConfiguration.locale.equals(Locale.US)) { 170 mOldLocale = oldConfiguration.locale; 171 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 172 Configuration newConfiguration = new Configuration(oldConfiguration); 173 newConfiguration.locale = Locale.US; 174 resources.updateConfiguration(newConfiguration, displayMetrics); 175 } 176 177 // Initialize the latches. 178 mCancelOperationCounter = new CallCounter(); 179 mLayoutCallCounter = new CallCounter(); 180 mFinishCallCounter = new CallCounter(); 181 mWriteCallCounter = new CallCounter(); 182 mFinishCallCounter = new CallCounter(); 183 mPrintJobQueuedCallCounter = new CallCounter(); 184 mDestroySessionCallCounter = new CallCounter(); 185 186 // Create the activity for the right locale. 187 createActivity(); 188 } 189 190 @Override tearDown()191 public void tearDown() throws Exception { 192 if (!supportsPrinting()) { 193 return; 194 } 195 196 // Done with the activity. 197 getActivity().finish(); 198 enableImes(); 199 200 // Restore the locale if needed. 201 if (mOldLocale != null) { 202 Resources resources = getInstrumentation().getTargetContext().getResources(); 203 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 204 Configuration newConfiguration = new Configuration(resources.getConfiguration()); 205 newConfiguration.locale = mOldLocale; 206 mOldLocale = null; 207 resources.updateConfiguration(newConfiguration, displayMetrics); 208 } 209 210 disablePrintServices(); 211 // Make sure the spooler is cleaned. 212 clearPrintSpoolerData(); 213 214 super.tearDown(); 215 } 216 print(final PrintDocumentAdapter adapter)217 protected void print(final PrintDocumentAdapter adapter) { 218 // Initiate printing as if coming from the app. 219 getInstrumentation().runOnMainSync(new Runnable() { 220 @Override 221 public void run() { 222 PrintManager printManager = (PrintManager) getActivity() 223 .getSystemService(Context.PRINT_SERVICE); 224 printManager.print("Print job", adapter, null); 225 } 226 }); 227 } 228 onCancelOperationCalled()229 protected void onCancelOperationCalled() { 230 mCancelOperationCounter.call(); 231 } 232 onLayoutCalled()233 protected void onLayoutCalled() { 234 mLayoutCallCounter.call(); 235 } 236 getWriteCallCount()237 protected int getWriteCallCount() { 238 return mWriteCallCounter.getCallCount(); 239 } 240 onWriteCalled()241 protected void onWriteCalled() { 242 mWriteCallCounter.call(); 243 } 244 onFinishCalled()245 protected void onFinishCalled() { 246 mFinishCallCounter.call(); 247 } 248 onPrintJobQueuedCalled()249 protected void onPrintJobQueuedCalled() { 250 mPrintJobQueuedCallCounter.call(); 251 } 252 onPrinterDiscoverySessionDestroyCalled()253 protected void onPrinterDiscoverySessionDestroyCalled() { 254 mDestroySessionCallCounter.call(); 255 } 256 waitForCancelOperationCallbackCalled()257 protected void waitForCancelOperationCallbackCalled() { 258 waitForCallbackCallCount(mCancelOperationCounter, 1, 259 "Did not get expected call to onCancel for the current operation."); 260 } 261 waitForPrinterDiscoverySessionDestroyCallbackCalled()262 protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() { 263 waitForCallbackCallCount(mDestroySessionCallCounter, 1, 264 "Did not get expected call to onDestroyPrinterDiscoverySession."); 265 } 266 waitForServiceOnPrintJobQueuedCallbackCalled()267 protected void waitForServiceOnPrintJobQueuedCallbackCalled() { 268 waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1, 269 "Did not get expected call to onPrintJobQueued."); 270 } 271 waitForAdapterFinishCallbackCalled()272 protected void waitForAdapterFinishCallbackCalled() { 273 waitForCallbackCallCount(mFinishCallCounter, 1, 274 "Did not get expected call to finish."); 275 } 276 waitForLayoutAdapterCallbackCount(int count)277 protected void waitForLayoutAdapterCallbackCount(int count) { 278 waitForCallbackCallCount(mLayoutCallCounter, count, 279 "Did not get expected call to layout."); 280 } 281 waitForWriteAdapterCallback()282 protected void waitForWriteAdapterCallback() { 283 waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write."); 284 } 285 waitForCallbackCallCount(CallCounter counter, int count, String message)286 private void waitForCallbackCallCount(CallCounter counter, int count, String message) { 287 try { 288 counter.waitForCount(count, OPERATION_TIMEOUT); 289 } catch (TimeoutException te) { 290 fail(message); 291 } 292 } 293 selectPrinter(String printerName)294 protected void selectPrinter(String printerName) throws UiObjectNotFoundException { 295 try { 296 UiObject destinationSpinner = new UiObject(new UiSelector().resourceId( 297 "com.android.printspooler:id/destination_spinner")); 298 destinationSpinner.click(); 299 UiObject printerOption = new UiObject(new UiSelector().text(printerName)); 300 printerOption.click(); 301 } catch (UiObjectNotFoundException e) { 302 dumpWindowHierarchy(); 303 throw new UiObjectNotFoundException(e); 304 } 305 } 306 changeOrientation(String orientation)307 protected void changeOrientation(String orientation) throws UiObjectNotFoundException { 308 try { 309 UiObject orientationSpinner = new UiObject(new UiSelector().resourceId( 310 "com.android.printspooler:id/orientation_spinner")); 311 orientationSpinner.click(); 312 UiObject orientationOption = new UiObject(new UiSelector().text(orientation)); 313 orientationOption.click(); 314 } catch (UiObjectNotFoundException e) { 315 dumpWindowHierarchy(); 316 throw new UiObjectNotFoundException(e); 317 } 318 } 319 changeMediaSize(String mediaSize)320 protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException { 321 try { 322 UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId( 323 "com.android.printspooler:id/paper_size_spinner")); 324 mediaSizeSpinner.click(); 325 UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize)); 326 mediaSizeOption.click(); 327 } catch (UiObjectNotFoundException e) { 328 dumpWindowHierarchy(); 329 throw new UiObjectNotFoundException(e); 330 } 331 } 332 changeColor(String color)333 protected void changeColor(String color) throws UiObjectNotFoundException { 334 try { 335 UiObject colorSpinner = new UiObject(new UiSelector().resourceId( 336 "com.android.printspooler:id/color_spinner")); 337 colorSpinner.click(); 338 UiObject colorOption = new UiObject(new UiSelector().text(color)); 339 colorOption.click(); 340 } catch (UiObjectNotFoundException e) { 341 dumpWindowHierarchy(); 342 throw new UiObjectNotFoundException(e); 343 } 344 } 345 changeDuplex(String duplex)346 protected void changeDuplex(String duplex) throws UiObjectNotFoundException { 347 try { 348 UiObject duplexSpinner = new UiObject(new UiSelector().resourceId( 349 "com.android.printspooler:id/duplex_spinner")); 350 duplexSpinner.click(); 351 UiObject duplexOption = new UiObject(new UiSelector().text(duplex)); 352 duplexOption.click(); 353 } catch (UiObjectNotFoundException e) { 354 dumpWindowHierarchy(); 355 throw new UiObjectNotFoundException(e); 356 } 357 } 358 clickPrintButton()359 protected void clickPrintButton() throws UiObjectNotFoundException { 360 try { 361 UiObject printButton = new UiObject(new UiSelector().resourceId( 362 "com.android.printspooler:id/print_button")); 363 printButton.click(); 364 } catch (UiObjectNotFoundException e) { 365 dumpWindowHierarchy(); 366 throw new UiObjectNotFoundException(e); 367 } 368 } 369 dumpWindowHierarchy()370 private void dumpWindowHierarchy() { 371 String name = "print-test-failure-" + System.currentTimeMillis() + ".xml"; 372 File file = new File(getActivity().getFilesDir(), name); 373 getUiDevice().dumpWindowHierarchy(file.toString()); 374 } 375 getActivity()376 protected PrintDocumentActivity getActivity() { 377 return mActivity; 378 } 379 createActivity()380 private void createActivity() { 381 mActivity = launchActivity( 382 getInstrumentation().getTargetContext().getPackageName(), 383 PrintDocumentActivity.class, null); 384 } 385 openPrintOptions()386 protected void openPrintOptions() throws UiObjectNotFoundException { 387 UiObject expandHandle = new UiObject(new UiSelector().resourceId( 388 "com.android.printspooler:id/expand_collapse_handle")); 389 expandHandle.click(); 390 } 391 clearPrintSpoolerData()392 protected void clearPrintSpoolerData() throws Exception { 393 assertTrue("failed to clear print spooler data", 394 SystemUtil.runShellCommand(getInstrumentation(), 395 String.format("pm clear %s", PRINT_SPOOLER_PACKAGE_NAME)) 396 .contains(PM_CLEAR_SUCCESS_OUTPUT)); 397 } 398 enablePrintServices()399 private void enablePrintServices() throws Exception { 400 String pkgName = getInstrumentation().getContext().getPackageName(); 401 String enabledServicesValue = String.format("%s/%s:%s/%s", 402 pkgName, FirstPrintService.class.getCanonicalName(), 403 pkgName, SecondPrintService.class.getCanonicalName()); 404 SystemUtil.runShellCommand(getInstrumentation(), 405 "settings put secure enabled_print_services " + enabledServicesValue); 406 } 407 disablePrintServices()408 private void disablePrintServices() throws Exception { 409 SystemUtil.runShellCommand(getInstrumentation(), 410 "settings put secure enabled_print_services \"\""); 411 } 412 verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)413 protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, 414 PrintAttributes oldAttributes, PrintAttributes newAttributes, 415 final boolean forPreview) { 416 inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes), 417 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat( 418 new BaseMatcher<Bundle>() { 419 @Override 420 public boolean matches(Object item) { 421 Bundle bundle = (Bundle) item; 422 return forPreview == bundle.getBoolean( 423 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW); 424 } 425 426 @Override 427 public void describeTo(Description description) { 428 /* do nothing */ 429 } 430 })); 431 } 432 createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)433 protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, 434 Answer<Void> writeAnswer, Answer<Void> finishAnswer) { 435 // Create a mock print adapter. 436 PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class); 437 if (layoutAnswer != null) { 438 doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class), 439 any(PrintAttributes.class), any(CancellationSignal.class), 440 any(LayoutResultCallback.class), any(Bundle.class)); 441 } 442 if (writeAnswer != null) { 443 doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class), 444 any(ParcelFileDescriptor.class), any(CancellationSignal.class), 445 any(WriteResultCallback.class)); 446 } 447 if (finishAnswer != null) { 448 doAnswer(finishAnswer).when(adapter).onFinish(); 449 } 450 return adapter; 451 } 452 453 @SuppressWarnings("unchecked") createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)454 protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 455 Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, 456 Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, 457 Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) { 458 PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class); 459 460 doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class)); 461 when(callbacks.getSession()).thenCallRealMethod(); 462 463 if (onStartPrinterDiscovery != null) { 464 doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery( 465 any(List.class)); 466 } 467 if (onStopPrinterDiscovery != null) { 468 doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery(); 469 } 470 if (onValidatePrinters != null) { 471 doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters( 472 any(List.class)); 473 } 474 if (onStartPrinterStateTracking != null) { 475 doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking( 476 any(PrinterId.class)); 477 } 478 if (onStopPrinterStateTracking != null) { 479 doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking( 480 any(PrinterId.class)); 481 } 482 if (onDestroy != null) { 483 doAnswer(onDestroy).when(callbacks).onDestroy(); 484 } 485 486 return callbacks; 487 } 488 createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)489 protected PrintServiceCallbacks createMockPrintServiceCallbacks( 490 Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, 491 Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) { 492 final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class); 493 494 doCallRealMethod().when(service).setService(any(PrintService.class)); 495 when(service.getService()).thenCallRealMethod(); 496 497 if (onCreatePrinterDiscoverySessionCallbacks != null) { 498 doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service) 499 .onCreatePrinterDiscoverySessionCallbacks(); 500 } 501 if (onPrintJobQueued != null) { 502 doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class)); 503 } 504 if (onRequestCancelPrintJob != null) { 505 doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob( 506 any(PrintJob.class)); 507 } 508 509 return service; 510 } 511 writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)512 protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, 513 int fromIndex, int toIndex) throws IOException { 514 PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints); 515 final int pageCount = toIndex - fromIndex + 1; 516 for (int i = 0; i < pageCount; i++) { 517 PdfDocument.Page page = document.startPage(i); 518 document.finishPage(page); 519 } 520 FileOutputStream fos = new FileOutputStream(output.getFileDescriptor()); 521 document.writeTo(fos); 522 document.close(); 523 } 524 525 protected final class CallCounter { 526 private final Object mLock = new Object(); 527 528 private int mCallCount; 529 call()530 public void call() { 531 synchronized (mLock) { 532 mCallCount++; 533 mLock.notifyAll(); 534 } 535 } 536 getCallCount()537 public int getCallCount() { 538 synchronized (mLock) { 539 return mCallCount; 540 } 541 } 542 waitForCount(int count, long timeoutMillis)543 public void waitForCount(int count, long timeoutMillis) throws TimeoutException { 544 synchronized (mLock) { 545 final long startTimeMillis = SystemClock.uptimeMillis(); 546 while (mCallCount < count) { 547 try { 548 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 549 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 550 if (remainingTimeMillis <= 0) { 551 throw new TimeoutException(); 552 } 553 mLock.wait(timeoutMillis); 554 } catch (InterruptedException ie) { 555 /* ignore */ 556 } 557 } 558 } 559 } 560 } 561 supportsPrinting()562 protected boolean supportsPrinting() { 563 return getInstrumentation().getContext().getPackageManager() 564 .hasSystemFeature(PackageManager.FEATURE_PRINTING); 565 } 566 } 567