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 setUp()142 public void setUp() throws Exception { 143 // Make sure we start with a clean slate. 144 clearPrintSpoolerData(); 145 enablePrintServices(); 146 disableImes(); 147 148 // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2 149 // Dexmaker is used by mockito. 150 System.setProperty("dexmaker.dexcache", getInstrumentation() 151 .getTargetContext().getCacheDir().getPath()); 152 153 // Set to US locale. 154 Resources resources = getInstrumentation().getTargetContext().getResources(); 155 Configuration oldConfiguration = resources.getConfiguration(); 156 if (!oldConfiguration.locale.equals(Locale.US)) { 157 mOldLocale = oldConfiguration.locale; 158 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 159 Configuration newConfiguration = new Configuration(oldConfiguration); 160 newConfiguration.locale = Locale.US; 161 resources.updateConfiguration(newConfiguration, displayMetrics); 162 } 163 164 // Initialize the latches. 165 mCancelOperationCounter = new CallCounter(); 166 mLayoutCallCounter = new CallCounter(); 167 mFinishCallCounter = new CallCounter(); 168 mWriteCallCounter = new CallCounter(); 169 mFinishCallCounter = new CallCounter(); 170 mPrintJobQueuedCallCounter = new CallCounter(); 171 mDestroySessionCallCounter = new CallCounter(); 172 173 // Create the activity for the right locale. 174 createActivity(); 175 } 176 177 @Override tearDown()178 public void tearDown() throws Exception { 179 // Done with the activity. 180 getActivity().finish(); 181 enableImes(); 182 183 // Restore the locale if needed. 184 if (mOldLocale != null) { 185 Resources resources = getInstrumentation().getTargetContext().getResources(); 186 DisplayMetrics displayMetrics = resources.getDisplayMetrics(); 187 Configuration newConfiguration = new Configuration(resources.getConfiguration()); 188 newConfiguration.locale = mOldLocale; 189 mOldLocale = null; 190 resources.updateConfiguration(newConfiguration, displayMetrics); 191 } 192 193 disablePrintServices(); 194 // Make sure the spooler is cleaned. 195 clearPrintSpoolerData(); 196 } 197 print(final PrintDocumentAdapter adapter)198 protected void print(final PrintDocumentAdapter adapter) { 199 // Initiate printing as if coming from the app. 200 getInstrumentation().runOnMainSync(new Runnable() { 201 @Override 202 public void run() { 203 PrintManager printManager = (PrintManager) getActivity() 204 .getSystemService(Context.PRINT_SERVICE); 205 printManager.print("Print job", adapter, null); 206 } 207 }); 208 } 209 onCancelOperationCalled()210 protected void onCancelOperationCalled() { 211 mCancelOperationCounter.call(); 212 } 213 onLayoutCalled()214 protected void onLayoutCalled() { 215 mLayoutCallCounter.call(); 216 } 217 getWriteCallCount()218 protected int getWriteCallCount() { 219 return mWriteCallCounter.getCallCount(); 220 } 221 onWriteCalled()222 protected void onWriteCalled() { 223 mWriteCallCounter.call(); 224 } 225 onFinishCalled()226 protected void onFinishCalled() { 227 mFinishCallCounter.call(); 228 } 229 onPrintJobQueuedCalled()230 protected void onPrintJobQueuedCalled() { 231 mPrintJobQueuedCallCounter.call(); 232 } 233 onPrinterDiscoverySessionDestroyCalled()234 protected void onPrinterDiscoverySessionDestroyCalled() { 235 mDestroySessionCallCounter.call(); 236 } 237 waitForCancelOperationCallbackCalled()238 protected void waitForCancelOperationCallbackCalled() { 239 waitForCallbackCallCount(mCancelOperationCounter, 1, 240 "Did not get expected call to onCancel for the current operation."); 241 } 242 waitForPrinterDiscoverySessionDestroyCallbackCalled()243 protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() { 244 waitForCallbackCallCount(mDestroySessionCallCounter, 1, 245 "Did not get expected call to onDestroyPrinterDiscoverySession."); 246 } 247 waitForServiceOnPrintJobQueuedCallbackCalled()248 protected void waitForServiceOnPrintJobQueuedCallbackCalled() { 249 waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1, 250 "Did not get expected call to onPrintJobQueued."); 251 } 252 waitForAdapterFinishCallbackCalled()253 protected void waitForAdapterFinishCallbackCalled() { 254 waitForCallbackCallCount(mFinishCallCounter, 1, 255 "Did not get expected call to finish."); 256 } 257 waitForLayoutAdapterCallbackCount(int count)258 protected void waitForLayoutAdapterCallbackCount(int count) { 259 waitForCallbackCallCount(mLayoutCallCounter, count, 260 "Did not get expected call to layout."); 261 } 262 waitForWriteAdapterCallback()263 protected void waitForWriteAdapterCallback() { 264 waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write."); 265 } 266 waitForCallbackCallCount(CallCounter counter, int count, String message)267 private void waitForCallbackCallCount(CallCounter counter, int count, String message) { 268 try { 269 counter.waitForCount(count, OPERATION_TIMEOUT); 270 } catch (TimeoutException te) { 271 fail(message); 272 } 273 } 274 selectPrinter(String printerName)275 protected void selectPrinter(String printerName) throws UiObjectNotFoundException { 276 try { 277 UiObject destinationSpinner = new UiObject(new UiSelector().resourceId( 278 "com.android.printspooler:id/destination_spinner")); 279 destinationSpinner.click(); 280 UiObject printerOption = new UiObject(new UiSelector().text(printerName)); 281 printerOption.click(); 282 } catch (UiObjectNotFoundException e) { 283 dumpWindowHierarchy(); 284 throw new UiObjectNotFoundException(e); 285 } 286 } 287 changeOrientation(String orientation)288 protected void changeOrientation(String orientation) throws UiObjectNotFoundException { 289 try { 290 UiObject orientationSpinner = new UiObject(new UiSelector().resourceId( 291 "com.android.printspooler:id/orientation_spinner")); 292 orientationSpinner.click(); 293 UiObject orientationOption = new UiObject(new UiSelector().text(orientation)); 294 orientationOption.click(); 295 } catch (UiObjectNotFoundException e) { 296 dumpWindowHierarchy(); 297 throw new UiObjectNotFoundException(e); 298 } 299 } 300 changeMediaSize(String mediaSize)301 protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException { 302 try { 303 UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId( 304 "com.android.printspooler:id/paper_size_spinner")); 305 mediaSizeSpinner.click(); 306 UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize)); 307 mediaSizeOption.click(); 308 } catch (UiObjectNotFoundException e) { 309 dumpWindowHierarchy(); 310 throw new UiObjectNotFoundException(e); 311 } 312 } 313 changeColor(String color)314 protected void changeColor(String color) throws UiObjectNotFoundException { 315 try { 316 UiObject colorSpinner = new UiObject(new UiSelector().resourceId( 317 "com.android.printspooler:id/color_spinner")); 318 colorSpinner.click(); 319 UiObject colorOption = new UiObject(new UiSelector().text(color)); 320 colorOption.click(); 321 } catch (UiObjectNotFoundException e) { 322 dumpWindowHierarchy(); 323 throw new UiObjectNotFoundException(e); 324 } 325 } 326 changeDuplex(String duplex)327 protected void changeDuplex(String duplex) throws UiObjectNotFoundException { 328 try { 329 UiObject duplexSpinner = new UiObject(new UiSelector().resourceId( 330 "com.android.printspooler:id/duplex_spinner")); 331 duplexSpinner.click(); 332 UiObject duplexOption = new UiObject(new UiSelector().text(duplex)); 333 duplexOption.click(); 334 } catch (UiObjectNotFoundException e) { 335 dumpWindowHierarchy(); 336 throw new UiObjectNotFoundException(e); 337 } 338 } 339 clickPrintButton()340 protected void clickPrintButton() throws UiObjectNotFoundException { 341 try { 342 UiObject printButton = new UiObject(new UiSelector().resourceId( 343 "com.android.printspooler:id/print_button")); 344 printButton.click(); 345 } catch (UiObjectNotFoundException e) { 346 dumpWindowHierarchy(); 347 throw new UiObjectNotFoundException(e); 348 } 349 } 350 dumpWindowHierarchy()351 private void dumpWindowHierarchy() { 352 String name = "print-test-failure-" + System.currentTimeMillis() + ".xml"; 353 File file = new File(getActivity().getFilesDir(), name); 354 getUiDevice().dumpWindowHierarchy(file.toString()); 355 } 356 getActivity()357 protected PrintDocumentActivity getActivity() { 358 return mActivity; 359 } 360 createActivity()361 private void createActivity() { 362 mActivity = launchActivity( 363 getInstrumentation().getTargetContext().getPackageName(), 364 PrintDocumentActivity.class, null); 365 } 366 openPrintOptions()367 protected void openPrintOptions() throws UiObjectNotFoundException { 368 UiObject expandHandle = new UiObject(new UiSelector().resourceId( 369 "com.android.printspooler:id/expand_collapse_handle")); 370 expandHandle.click(); 371 } 372 clearPrintSpoolerData()373 protected void clearPrintSpoolerData() throws Exception { 374 assertTrue("failed to clear print spooler data", 375 SystemUtil.runShellCommand(getInstrumentation(), 376 String.format("pm clear %s", PRINT_SPOOLER_PACKAGE_NAME)) 377 .contains(PM_CLEAR_SUCCESS_OUTPUT)); 378 } 379 enablePrintServices()380 private void enablePrintServices() throws Exception { 381 String pkgName = getInstrumentation().getContext().getPackageName(); 382 String enabledServicesValue = String.format("%s/%s:%s/%s", 383 pkgName, FirstPrintService.class.getCanonicalName(), 384 pkgName, SecondPrintService.class.getCanonicalName()); 385 SystemUtil.runShellCommand(getInstrumentation(), 386 "settings put secure enabled_print_services " + enabledServicesValue); 387 } 388 disablePrintServices()389 private void disablePrintServices() throws Exception { 390 SystemUtil.runShellCommand(getInstrumentation(), 391 "settings put secure enabled_print_services \"\""); 392 } 393 verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)394 protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, 395 PrintAttributes oldAttributes, PrintAttributes newAttributes, 396 final boolean forPreview) { 397 inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes), 398 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat( 399 new BaseMatcher<Bundle>() { 400 @Override 401 public boolean matches(Object item) { 402 Bundle bundle = (Bundle) item; 403 return forPreview == bundle.getBoolean( 404 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW); 405 } 406 407 @Override 408 public void describeTo(Description description) { 409 /* do nothing */ 410 } 411 })); 412 } 413 createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)414 protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, 415 Answer<Void> writeAnswer, Answer<Void> finishAnswer) { 416 // Create a mock print adapter. 417 PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class); 418 if (layoutAnswer != null) { 419 doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class), 420 any(PrintAttributes.class), any(CancellationSignal.class), 421 any(LayoutResultCallback.class), any(Bundle.class)); 422 } 423 if (writeAnswer != null) { 424 doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class), 425 any(ParcelFileDescriptor.class), any(CancellationSignal.class), 426 any(WriteResultCallback.class)); 427 } 428 if (finishAnswer != null) { 429 doAnswer(finishAnswer).when(adapter).onFinish(); 430 } 431 return adapter; 432 } 433 434 @SuppressWarnings("unchecked") createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)435 protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 436 Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, 437 Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, 438 Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) { 439 PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class); 440 441 doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class)); 442 when(callbacks.getSession()).thenCallRealMethod(); 443 444 if (onStartPrinterDiscovery != null) { 445 doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery( 446 any(List.class)); 447 } 448 if (onStopPrinterDiscovery != null) { 449 doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery(); 450 } 451 if (onValidatePrinters != null) { 452 doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters( 453 any(List.class)); 454 } 455 if (onStartPrinterStateTracking != null) { 456 doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking( 457 any(PrinterId.class)); 458 } 459 if (onStopPrinterStateTracking != null) { 460 doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking( 461 any(PrinterId.class)); 462 } 463 if (onDestroy != null) { 464 doAnswer(onDestroy).when(callbacks).onDestroy(); 465 } 466 467 return callbacks; 468 } 469 createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)470 protected PrintServiceCallbacks createMockPrintServiceCallbacks( 471 Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, 472 Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) { 473 final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class); 474 475 doCallRealMethod().when(service).setService(any(PrintService.class)); 476 when(service.getService()).thenCallRealMethod(); 477 478 if (onCreatePrinterDiscoverySessionCallbacks != null) { 479 doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service) 480 .onCreatePrinterDiscoverySessionCallbacks(); 481 } 482 if (onPrintJobQueued != null) { 483 doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class)); 484 } 485 if (onRequestCancelPrintJob != null) { 486 doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob( 487 any(PrintJob.class)); 488 } 489 490 return service; 491 } 492 writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)493 protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, 494 int fromIndex, int toIndex) throws IOException { 495 PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints); 496 final int pageCount = toIndex - fromIndex + 1; 497 for (int i = 0; i < pageCount; i++) { 498 PdfDocument.Page page = document.startPage(i); 499 document.finishPage(page); 500 } 501 FileOutputStream fos = new FileOutputStream(output.getFileDescriptor()); 502 document.writeTo(fos); 503 document.close(); 504 } 505 506 protected final class CallCounter { 507 private final Object mLock = new Object(); 508 509 private int mCallCount; 510 call()511 public void call() { 512 synchronized (mLock) { 513 mCallCount++; 514 mLock.notifyAll(); 515 } 516 } 517 getCallCount()518 public int getCallCount() { 519 synchronized (mLock) { 520 return mCallCount; 521 } 522 } 523 waitForCount(int count, long timeoutMillis)524 public void waitForCount(int count, long timeoutMillis) throws TimeoutException { 525 synchronized (mLock) { 526 final long startTimeMillis = SystemClock.uptimeMillis(); 527 while (mCallCount < count) { 528 try { 529 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 530 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 531 if (remainingTimeMillis <= 0) { 532 throw new TimeoutException(); 533 } 534 mLock.wait(timeoutMillis); 535 } catch (InterruptedException ie) { 536 /* ignore */ 537 } 538 } 539 } 540 } 541 } 542 supportsPrinting()543 protected boolean supportsPrinting() { 544 return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING); 545 } 546 } 547