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.test; 18 19 import static android.content.pm.PackageManager.GET_META_DATA; 20 import static android.content.pm.PackageManager.GET_SERVICES; 21 import static android.print.test.Utils.eventually; 22 import static android.print.test.Utils.getPrintManager; 23 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertTrue; 26 import static org.junit.Assert.fail; 27 import static org.junit.Assume.assumeTrue; 28 import static org.mockito.Matchers.any; 29 import static org.mockito.Matchers.eq; 30 import static org.mockito.Mockito.doAnswer; 31 import static org.mockito.Mockito.doCallRealMethod; 32 import static org.mockito.Mockito.mock; 33 import static org.mockito.Mockito.when; 34 import static org.mockito.hamcrest.MockitoHamcrest.argThat; 35 36 import android.app.Activity; 37 import android.app.Instrumentation; 38 import android.app.UiAutomation; 39 import android.content.ComponentName; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.pm.PackageManager; 43 import android.content.pm.ResolveInfo; 44 import android.graphics.pdf.PdfDocument; 45 import android.os.Bundle; 46 import android.os.CancellationSignal; 47 import android.os.ParcelFileDescriptor; 48 import android.os.SystemClock; 49 import android.platform.helpers.exceptions.TestHelperException; 50 import android.print.PageRange; 51 import android.print.PrintAttributes; 52 import android.print.PrintDocumentAdapter; 53 import android.print.PrintDocumentAdapter.LayoutResultCallback; 54 import android.print.PrintDocumentAdapter.WriteResultCallback; 55 import android.print.PrintDocumentInfo; 56 import android.print.PrintManager; 57 import android.print.PrinterId; 58 import android.print.pdf.PrintedPdfDocument; 59 import android.print.test.services.PrintServiceCallbacks; 60 import android.print.test.services.PrinterDiscoverySessionCallbacks; 61 import android.print.test.services.StubbablePrintService; 62 import android.print.test.services.StubbablePrinterDiscoverySession; 63 import android.printservice.CustomPrinterIconCallback; 64 import android.printservice.PrintJob; 65 import android.provider.Settings; 66 import android.util.Log; 67 import android.util.SparseArray; 68 69 import androidx.annotation.NonNull; 70 import androidx.annotation.Nullable; 71 import androidx.test.InstrumentationRegistry; 72 73 import com.android.compatibility.common.util.SystemUtil; 74 import com.android.cts.helpers.DeviceInteractionHelperRule; 75 import com.android.cts.helpers.ICtsPrintHelper; 76 77 import org.hamcrest.BaseMatcher; 78 import org.hamcrest.Description; 79 import org.junit.After; 80 import org.junit.AfterClass; 81 import org.junit.Before; 82 import org.junit.BeforeClass; 83 import org.junit.Rule; 84 import org.junit.rules.TestRule; 85 import org.junit.runners.model.Statement; 86 import org.mockito.InOrder; 87 import org.mockito.stubbing.Answer; 88 89 import java.io.BufferedReader; 90 import java.io.FileInputStream; 91 import java.io.FileOutputStream; 92 import java.io.IOException; 93 import java.io.InputStreamReader; 94 import java.lang.annotation.Annotation; 95 import java.lang.annotation.ElementType; 96 import java.lang.annotation.Retention; 97 import java.lang.annotation.RetentionPolicy; 98 import java.lang.annotation.Target; 99 import java.util.ArrayList; 100 import java.util.List; 101 import java.util.Objects; 102 import java.util.concurrent.TimeoutException; 103 import java.util.concurrent.atomic.AtomicInteger; 104 105 /** 106 * This is the base class for print tests. 107 */ 108 public abstract class BasePrintTest { 109 private static final String LOG_TAG = "BasePrintTest"; 110 111 protected static final long OPERATION_TIMEOUT_MILLIS = 20000; 112 protected static final String PRINT_JOB_NAME = "Test"; 113 static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID"; 114 115 private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; 116 private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success"; 117 private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s"; 118 private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable "; 119 private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable "; 120 private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT 121 private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler"; 122 private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000; 123 124 private static final AtomicInteger sLastTestID = new AtomicInteger(); 125 private int mTestId; 126 private PrintDocumentActivity mActivity; 127 128 private static String sDisabledPrintServicesBefore; 129 130 private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>(); 131 132 public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity(); 133 public final @Rule DeviceInteractionHelperRule<ICtsPrintHelper> mHelperRule = 134 new DeviceInteractionHelperRule(ICtsPrintHelper.class); 135 136 protected ICtsPrintHelper mPrintHelper; 137 138 private CallCounter mCancelOperationCounter; 139 private CallCounter mLayoutCallCounter; 140 private CallCounter mWriteCallCounter; 141 private CallCounter mWriteCancelCallCounter; 142 private CallCounter mStartCallCounter; 143 private CallCounter mFinishCallCounter; 144 private CallCounter mPrintJobQueuedCallCounter; 145 private CallCounter mCreateSessionCallCounter; 146 private CallCounter mDestroySessionCallCounter; 147 private CallCounter mDestroyActivityCallCounter = new CallCounter(); 148 private CallCounter mCreateActivityCallCounter = new CallCounter(); 149 150 private static String[] sEnabledImes; 151 getEnabledImes()152 private static String[] getEnabledImes() throws IOException { 153 List<String> imeList = new ArrayList<>(); 154 155 ParcelFileDescriptor pfd = getUiAutomation() 156 .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS); 157 try (BufferedReader reader = new BufferedReader( 158 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) { 159 160 String line; 161 while ((line = reader.readLine()) != null) { 162 imeList.add(line); 163 } 164 } 165 166 String[] imeArray = new String[imeList.size()]; 167 imeList.toArray(imeArray); 168 169 return imeArray; 170 } 171 disableImes()172 private static void disableImes() throws Exception { 173 sEnabledImes = getEnabledImes(); 174 for (String ime : sEnabledImes) { 175 String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime; 176 SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand); 177 } 178 } 179 enableImes()180 private static void enableImes() throws Exception { 181 for (String ime : sEnabledImes) { 182 String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime; 183 SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand); 184 } 185 sEnabledImes = null; 186 } 187 getInstrumentation()188 public static Instrumentation getInstrumentation() { 189 return InstrumentationRegistry.getInstrumentation(); 190 } 191 getUiAutomation()192 public static UiAutomation getUiAutomation() { 193 long start = SystemClock.uptimeMillis(); 194 while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) { 195 UiAutomation ui = getInstrumentation().getUiAutomation(); 196 if (ui != null) { 197 return ui; 198 } 199 } 200 201 throw new AssertionError("Failed to get UiAutomation"); 202 } 203 204 @BeforeClass setUpClass()205 public static void setUpClass() throws Exception { 206 Log.d(LOG_TAG, "setUpClass()"); 207 208 Instrumentation instrumentation = getInstrumentation(); 209 210 // Make sure we start with a clean slate. 211 Log.d(LOG_TAG, "clearPrintSpoolerData()"); 212 clearPrintSpoolerData(); 213 Log.d(LOG_TAG, "disableImes()"); 214 disableImes(); 215 Log.d(LOG_TAG, "disablePrintServices()"); 216 sDisabledPrintServicesBefore = disablePrintServices(instrumentation.getTargetContext() 217 .getPackageName()); 218 219 // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2 220 // Dexmaker is used by mockito. 221 System.setProperty("dexmaker.dexcache", instrumentation 222 .getTargetContext().getCacheDir().getPath()); 223 224 Log.d(LOG_TAG, "setUpClass() done"); 225 } 226 227 /** 228 * Disable all print services beside the ones we want to leave enabled. 229 * 230 * @param packageToLeaveEnabled The package of the services to leave enabled. 231 * 232 * @return Services that were enabled before this method was called 233 */ disablePrintServices(@ullable String packageToLeaveEnabled)234 protected static @NonNull String disablePrintServices(@Nullable String packageToLeaveEnabled) 235 throws IOException { 236 Instrumentation instrumentation = getInstrumentation(); 237 238 String previousEnabledServices = SystemUtil.runShellCommand(instrumentation, 239 "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES); 240 241 Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); 242 List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager() 243 .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA); 244 245 StringBuilder builder = new StringBuilder(); 246 for (ResolveInfo service : installedServices) { 247 if (packageToLeaveEnabled != null 248 && packageToLeaveEnabled.equals(service.serviceInfo.packageName)) { 249 continue; 250 } 251 if (builder.length() > 0) { 252 builder.append(":"); 253 } 254 builder.append(new ComponentName(service.serviceInfo.packageName, 255 service.serviceInfo.name).flattenToString()); 256 } 257 258 SystemUtil.runShellCommand(instrumentation, "settings put secure " 259 + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder); 260 261 return previousEnabledServices; 262 } 263 264 /** 265 * Revert {@link #disablePrintServices(String)} 266 * 267 * @param servicesToEnable Services that should be enabled 268 */ enablePrintServices(@onNull String servicesToEnable)269 protected static void enablePrintServices(@NonNull String servicesToEnable) throws IOException { 270 SystemUtil.runShellCommand(getInstrumentation(), 271 "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " " 272 + servicesToEnable); 273 } 274 275 @Before setUp()276 public void setUp() throws Exception { 277 Log.d(LOG_TAG, "setUp()"); 278 279 assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature( 280 PackageManager.FEATURE_PRINTING)); 281 282 // Initialize the latches. 283 Log.d(LOG_TAG, "init counters"); 284 mCancelOperationCounter = new CallCounter(); 285 mLayoutCallCounter = new CallCounter(); 286 mStartCallCounter = new CallCounter(); 287 mFinishCallCounter = new CallCounter(); 288 mWriteCallCounter = new CallCounter(); 289 mWriteCancelCallCounter = new CallCounter(); 290 mFinishCallCounter = new CallCounter(); 291 mPrintJobQueuedCallCounter = new CallCounter(); 292 mCreateSessionCallCounter = new CallCounter(); 293 mDestroySessionCallCounter = new CallCounter(); 294 295 mTestId = sLastTestID.incrementAndGet(); 296 sIdToTest.put(mTestId, this); 297 298 // Create the activity if needed 299 if (!mShouldStartActivityRule.mNoActivity) { 300 createActivity(); 301 } 302 303 mPrintHelper = mHelperRule.getHelper(); 304 mPrintHelper.setUp(); 305 306 Log.d(LOG_TAG, "setUp() done"); 307 } 308 309 @After tearDown()310 public void tearDown() throws Exception { 311 Log.d(LOG_TAG, "tearDown()"); 312 313 if (mPrintHelper != null) { 314 mPrintHelper.tearDown(); 315 } 316 317 finishActivity(); 318 319 sIdToTest.remove(mTestId); 320 321 Log.d(LOG_TAG, "tearDown() done"); 322 } 323 324 @AfterClass tearDownClass()325 public static void tearDownClass() throws Exception { 326 Log.d(LOG_TAG, "tearDownClass()"); 327 328 Instrumentation instrumentation = getInstrumentation(); 329 330 Log.d(LOG_TAG, "enablePrintServices()"); 331 enablePrintServices(sDisabledPrintServicesBefore); 332 333 Log.d(LOG_TAG, "enableImes()"); 334 enableImes(); 335 336 // Make sure the spooler is cleaned, this also un-approves all services 337 Log.d(LOG_TAG, "clearPrintSpoolerData()"); 338 clearPrintSpoolerData(); 339 340 SystemUtil.runShellCommand(instrumentation, "settings put secure " 341 + Settings.Secure.DISABLED_PRINT_SERVICES + " null"); 342 343 Log.d(LOG_TAG, "tearDownClass() done"); 344 } 345 print(@onNull final PrintDocumentAdapter adapter, final PrintAttributes attributes)346 protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter, 347 final PrintAttributes attributes) { 348 android.print.PrintJob[] printJob = new android.print.PrintJob[1]; 349 // Initiate printing as if coming from the app. 350 getInstrumentation().runOnMainSync(() -> { 351 PrintManager printManager = (PrintManager) getActivity() 352 .getSystemService(Context.PRINT_SERVICE); 353 printJob[0] = printManager.print("Print job", adapter, attributes); 354 }); 355 356 return printJob[0]; 357 } 358 print(PrintDocumentAdapter adapter)359 protected void print(PrintDocumentAdapter adapter) { 360 print(adapter, (PrintAttributes) null); 361 } 362 print(PrintDocumentAdapter adapter, String printJobName)363 protected void print(PrintDocumentAdapter adapter, String printJobName) { 364 print(adapter, printJobName, null); 365 } 366 367 /** 368 * Start printing 369 * 370 * @param adapter Adapter supplying data to print 371 * @param printJobName The name of the print job 372 */ print(@onNull PrintDocumentAdapter adapter, @NonNull String printJobName, @Nullable PrintAttributes attributes)373 protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName, 374 @Nullable PrintAttributes attributes) { 375 // Initiate printing as if coming from the app. 376 getInstrumentation() 377 .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter, 378 attributes)); 379 } 380 onCancelOperationCalled()381 protected void onCancelOperationCalled() { 382 mCancelOperationCounter.call(); 383 } 384 onStartCalled()385 public void onStartCalled() { 386 mStartCallCounter.call(); 387 } 388 onLayoutCalled()389 protected void onLayoutCalled() { 390 mLayoutCallCounter.call(); 391 } 392 getWriteCallCount()393 protected int getWriteCallCount() { 394 return mWriteCallCounter.getCallCount(); 395 } 396 onWriteCalled()397 protected void onWriteCalled() { 398 mWriteCallCounter.call(); 399 } 400 onWriteCancelCalled()401 protected void onWriteCancelCalled() { 402 mWriteCancelCallCounter.call(); 403 } 404 onFinishCalled()405 protected void onFinishCalled() { 406 mFinishCallCounter.call(); 407 } 408 onPrintJobQueuedCalled()409 protected void onPrintJobQueuedCalled() { 410 mPrintJobQueuedCallCounter.call(); 411 } 412 onPrinterDiscoverySessionCreateCalled()413 protected void onPrinterDiscoverySessionCreateCalled() { 414 mCreateSessionCallCounter.call(); 415 } 416 onPrinterDiscoverySessionDestroyCalled()417 protected void onPrinterDiscoverySessionDestroyCalled() { 418 mDestroySessionCallCounter.call(); 419 } 420 waitForCancelOperationCallbackCalled()421 protected void waitForCancelOperationCallbackCalled() { 422 waitForCallbackCallCount(mCancelOperationCounter, 1, 423 "Did not get expected call to onCancel for the current operation."); 424 } 425 waitForPrinterDiscoverySessionCreateCallbackCalled()426 protected void waitForPrinterDiscoverySessionCreateCallbackCalled() { 427 waitForCallbackCallCount(mCreateSessionCallCounter, 1, 428 "Did not get expected call to onCreatePrinterDiscoverySession."); 429 } 430 waitForPrinterDiscoverySessionDestroyCallbackCalled(int count)431 public void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) { 432 waitForCallbackCallCount(mDestroySessionCallCounter, count, 433 "Did not get expected call to onDestroyPrinterDiscoverySession."); 434 } 435 waitForServiceOnPrintJobQueuedCallbackCalled(int count)436 protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) { 437 waitForCallbackCallCount(mPrintJobQueuedCallCounter, count, 438 "Did not get expected call to onPrintJobQueued."); 439 } 440 waitForAdapterStartCallbackCalled()441 protected void waitForAdapterStartCallbackCalled() { 442 waitForCallbackCallCount(mStartCallCounter, 1, 443 "Did not get expected call to start."); 444 } 445 waitForAdapterFinishCallbackCalled()446 protected void waitForAdapterFinishCallbackCalled() { 447 waitForCallbackCallCount(mFinishCallCounter, 1, 448 "Did not get expected call to finish."); 449 } 450 waitForLayoutAdapterCallbackCount(int count)451 protected void waitForLayoutAdapterCallbackCount(int count) { 452 waitForCallbackCallCount(mLayoutCallCounter, count, 453 "Did not get expected call to layout."); 454 } 455 waitForWriteAdapterCallback(int count)456 public void waitForWriteAdapterCallback(int count) { 457 waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write."); 458 } 459 waitForWriteCancelCallback(int count)460 protected void waitForWriteCancelCallback(int count) { 461 waitForCallbackCallCount(mWriteCancelCallCounter, count, 462 "Did not get expected cancel of write."); 463 } 464 waitForCallbackCallCount(CallCounter counter, int count, String message)465 private static void waitForCallbackCallCount(CallCounter counter, int count, String message) { 466 try { 467 counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS); 468 } catch (TimeoutException te) { 469 fail(message); 470 } 471 } 472 473 /** 474 * Indicate the print activity was created. 475 */ onActivityCreateCalled(int testId, PrintDocumentActivity activity)476 static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) { 477 synchronized (sIdToTest) { 478 BasePrintTest test = sIdToTest.get(testId); 479 if (test != null) { 480 test.mActivity = activity; 481 test.mCreateActivityCallCounter.call(); 482 } 483 } 484 } 485 486 /** 487 * Indicate the print activity was destroyed. 488 */ onActivityDestroyCalled(int testId)489 static void onActivityDestroyCalled(int testId) { 490 synchronized (sIdToTest) { 491 BasePrintTest test = sIdToTest.get(testId); 492 if (test != null) { 493 test.mDestroyActivityCallCounter.call(); 494 } 495 } 496 } 497 finishActivity()498 private void finishActivity() { 499 Activity activity = mActivity; 500 501 if (activity != null) { 502 if (!activity.isFinishing()) { 503 activity.finish(); 504 } 505 506 while (!activity.isDestroyed()) { 507 int creates = mCreateActivityCallCounter.getCallCount(); 508 waitForCallbackCallCount(mDestroyActivityCallCounter, creates, 509 "Activity was not destroyed"); 510 } 511 } 512 } 513 514 /** 515 * Get the number of ties the print activity was destroyed. 516 * 517 * @return The number of onDestroy calls on the print activity. 518 */ getActivityDestroyCallbackCallCount()519 protected int getActivityDestroyCallbackCallCount() { 520 return mDestroyActivityCallCounter.getCallCount(); 521 } 522 523 /** 524 * Get the number of ties the print activity was created. 525 * 526 * @return The number of onCreate calls on the print activity. 527 */ getActivityCreateCallbackCallCount()528 private int getActivityCreateCallbackCallCount() { 529 return mCreateActivityCallCounter.getCallCount(); 530 } 531 532 /** 533 * Wait until create was called {@code count} times. 534 * 535 * @param count The number of create calls to expect. 536 */ waitForActivityCreateCallbackCalled(int count)537 private void waitForActivityCreateCallbackCalled(int count) { 538 waitForCallbackCallCount(mCreateActivityCallCounter, count, 539 "Did not get expected call to create."); 540 } 541 542 /** 543 * Reset all counters. 544 */ resetCounters()545 public void resetCounters() { 546 mCancelOperationCounter.reset(); 547 mLayoutCallCounter.reset(); 548 mWriteCallCounter.reset(); 549 mWriteCancelCallCounter.reset(); 550 mStartCallCounter.reset(); 551 mFinishCallCounter.reset(); 552 mPrintJobQueuedCallCounter.reset(); 553 mCreateSessionCallCounter.reset(); 554 mDestroySessionCallCounter.reset(); 555 mDestroyActivityCallCounter.reset(); 556 mCreateActivityCallCounter.reset(); 557 } 558 559 /** 560 * Wait until the message is shown that indicates that a printer is unavailable. 561 * 562 * @throws Exception If anything was unexpected. 563 */ waitForPrinterUnavailable()564 protected void waitForPrinterUnavailable() throws Exception { 565 final String printerUnavailableMessage = "This printer isn\'t available right now."; 566 567 String message = mPrintHelper.getStatusMessage(); 568 if (!message.equals(printerUnavailableMessage)) { 569 throw new Exception("Wrong message: " + message + " instead of " 570 + printerUnavailableMessage); 571 } 572 } 573 selectPrinter(String printerName)574 protected void selectPrinter(String printerName) throws IOException, TestHelperException { 575 mPrintHelper.selectPrinter(printerName, OPERATION_TIMEOUT_MILLIS); 576 } 577 selectPrinter(String printerName, long timeout)578 protected void selectPrinter(String printerName, long timeout) throws IOException, 579 TestHelperException { 580 mPrintHelper.selectPrinter(printerName, timeout); 581 } 582 answerPrintServicesWarning(boolean confirm)583 protected void answerPrintServicesWarning(boolean confirm) throws TestHelperException { 584 mPrintHelper.answerPrintServicesWarning(confirm); 585 } 586 changeOrientation(String orientation)587 protected void changeOrientation(String orientation) throws TestHelperException { 588 mPrintHelper.setPageOrientation(orientation); 589 } 590 getOrientation()591 public String getOrientation() throws TestHelperException { 592 return mPrintHelper.getPageOrientation(); 593 } 594 changeMediaSize(String mediaSize)595 protected void changeMediaSize(String mediaSize) throws TestHelperException { 596 mPrintHelper.setMediaSize(mediaSize); 597 } 598 changeColor(String color)599 protected void changeColor(String color) throws TestHelperException { 600 mPrintHelper.setColorMode(color); 601 } 602 getColor()603 public String getColor() throws TestHelperException { 604 return mPrintHelper.getColorMode(); 605 } 606 changeDuplex(String duplex)607 protected void changeDuplex(String duplex) throws TestHelperException { 608 mPrintHelper.setDuplexMode(duplex); 609 } 610 changeCopies(int newCopies)611 protected void changeCopies(int newCopies) throws TestHelperException { 612 mPrintHelper.setCopies(newCopies); 613 } 614 getCopies()615 protected int getCopies() throws TestHelperException { 616 return mPrintHelper.getCopies(); 617 } 618 assertNoPrintButton()619 protected void assertNoPrintButton() throws TestHelperException { 620 assertFalse(mPrintHelper.canSubmitJob()); 621 } 622 clickRetryButton()623 protected void clickRetryButton() throws TestHelperException { 624 mPrintHelper.retryPrintJob(); 625 } 626 getActivity()627 protected PrintDocumentActivity getActivity() { 628 return mActivity; 629 } 630 createActivity()631 protected void createActivity() throws IOException { 632 Log.d(LOG_TAG, "createActivity()"); 633 634 int createBefore = getActivityCreateCallbackCallCount(); 635 636 Intent intent = new Intent(Intent.ACTION_MAIN); 637 intent.putExtra(TEST_ID, mTestId); 638 639 Instrumentation instrumentation = getInstrumentation(); 640 641 // Unlock screen. 642 SystemUtil.runShellCommand(instrumentation, "input keyevent KEYCODE_WAKEUP"); 643 644 intent.setClassName(instrumentation.getTargetContext().getPackageName(), 645 PrintDocumentActivity.class.getName()); 646 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 647 instrumentation.startActivitySync(intent); 648 649 waitForActivityCreateCallbackCalled(createBefore + 1); 650 } 651 openPrintOptions()652 protected void openPrintOptions() throws TestHelperException { 653 mPrintHelper.openPrintOptions(); 654 } 655 openCustomPrintOptions()656 protected void openCustomPrintOptions() throws TestHelperException { 657 mPrintHelper.openCustomPrintOptions(); 658 } 659 clearPrintSpoolerData()660 protected static void clearPrintSpoolerData() throws Exception { 661 if (getInstrumentation().getContext().getPackageManager().hasSystemFeature( 662 PackageManager.FEATURE_PRINTING)) { 663 assertTrue("failed to clear print spooler data", 664 SystemUtil.runShellCommand(getInstrumentation(), String.format( 665 "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME)) 666 .contains(PM_CLEAR_SUCCESS_OUTPUT)); 667 } 668 } 669 verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)670 protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, 671 PrintAttributes oldAttributes, PrintAttributes newAttributes, 672 final boolean forPreview) { 673 inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes), 674 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat( 675 new BaseMatcher<Bundle>() { 676 @Override 677 public boolean matches(Object item) { 678 Bundle bundle = (Bundle) item; 679 return forPreview == bundle.getBoolean( 680 PrintDocumentAdapter.EXTRA_PRINT_PREVIEW); 681 } 682 683 @Override 684 public void describeTo(Description description) { 685 /* do nothing */ 686 } 687 })); 688 } 689 createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)690 protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, 691 Answer<Void> writeAnswer, Answer<Void> finishAnswer) { 692 // Create a mock print adapter. 693 PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class); 694 if (layoutAnswer != null) { 695 doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class), 696 any(PrintAttributes.class), any(CancellationSignal.class), 697 any(LayoutResultCallback.class), any(Bundle.class)); 698 } 699 if (writeAnswer != null) { 700 doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class), 701 any(ParcelFileDescriptor.class), any(CancellationSignal.class), 702 any(WriteResultCallback.class)); 703 } 704 if (finishAnswer != null) { 705 doAnswer(finishAnswer).when(adapter).onFinish(); 706 } 707 return adapter; 708 } 709 710 /** 711 * Create a mock {@link PrintDocumentAdapter} that provides one empty page. 712 * 713 * @return The mock adapter 714 */ createDefaultPrintDocumentAdapter(int numPages)715 public @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) { 716 final PrintAttributes[] printAttributes = new PrintAttributes[1]; 717 718 return createMockPrintDocumentAdapter( 719 invocation -> { 720 PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0]; 721 printAttributes[0] = (PrintAttributes) invocation.getArguments()[1]; 722 LayoutResultCallback callback = 723 (LayoutResultCallback) invocation 724 .getArguments()[3]; 725 726 callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME) 727 .setPageCount(numPages).build(), 728 !Objects.equals(oldAttributes, printAttributes[0])); 729 730 onLayoutCalled(); 731 return null; 732 }, invocation -> { 733 Object[] args = invocation.getArguments(); 734 PageRange[] pages = (PageRange[]) args[0]; 735 ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1]; 736 WriteResultCallback callback = 737 (WriteResultCallback) args[3]; 738 739 writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd()); 740 fd.close(); 741 callback.onWriteFinished(pages); 742 743 onWriteCalled(); 744 return null; 745 }, invocation -> { 746 onFinishCalled(); 747 return null; 748 }); 749 } 750 751 @SuppressWarnings("unchecked") createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)752 protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 753 Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, 754 Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, 755 Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking, 756 Answer<Void> onDestroy) { 757 PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class); 758 759 doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class)); 760 when(callbacks.getSession()).thenCallRealMethod(); 761 762 if (onStartPrinterDiscovery != null) { 763 doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery( 764 any(List.class)); 765 } 766 if (onStopPrinterDiscovery != null) { 767 doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery(); 768 } 769 if (onValidatePrinters != null) { 770 doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters( 771 any(List.class)); 772 } 773 if (onStartPrinterStateTracking != null) { 774 doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking( 775 any(PrinterId.class)); 776 } 777 if (onRequestCustomPrinterIcon != null) { 778 doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon( 779 any(PrinterId.class), any(CancellationSignal.class), 780 any(CustomPrinterIconCallback.class)); 781 } 782 if (onStopPrinterStateTracking != null) { 783 doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking( 784 any(PrinterId.class)); 785 } 786 if (onDestroy != null) { 787 doAnswer(onDestroy).when(callbacks).onDestroy(); 788 } 789 790 return callbacks; 791 } 792 createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)793 protected PrintServiceCallbacks createMockPrintServiceCallbacks( 794 Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, 795 Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) { 796 final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class); 797 798 doCallRealMethod().when(service).setService(any(StubbablePrintService.class)); 799 when(service.getService()).thenCallRealMethod(); 800 801 if (onCreatePrinterDiscoverySessionCallbacks != null) { 802 doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service) 803 .onCreatePrinterDiscoverySessionCallbacks(); 804 } 805 if (onPrintJobQueued != null) { 806 doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class)); 807 } 808 if (onRequestCancelPrintJob != null) { 809 doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob( 810 any(PrintJob.class)); 811 } 812 813 return service; 814 } 815 writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)816 protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, 817 int fromIndex, int toIndex) throws IOException { 818 PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints); 819 final int pageCount = toIndex - fromIndex + 1; 820 for (int i = 0; i < pageCount; i++) { 821 PdfDocument.Page page = document.startPage(i); 822 document.finishPage(page); 823 } 824 FileOutputStream fos = new FileOutputStream(output.getFileDescriptor()); 825 document.writeTo(fos); 826 fos.flush(); 827 document.close(); 828 } 829 selectPages(String pages, int totalPages)830 protected void selectPages(String pages, int totalPages) throws Exception { 831 mPrintHelper.setPageRange(pages, totalPages); 832 } 833 834 private static final class CallCounter { 835 private final Object mLock = new Object(); 836 837 private int mCallCount; 838 call()839 public void call() { 840 synchronized (mLock) { 841 mCallCount++; 842 mLock.notifyAll(); 843 } 844 } 845 getCallCount()846 int getCallCount() { 847 synchronized (mLock) { 848 return mCallCount; 849 } 850 } 851 reset()852 public void reset() { 853 synchronized (mLock) { 854 mCallCount = 0; 855 } 856 } 857 waitForCount(int count, long timeoutMillis)858 public void waitForCount(int count, long timeoutMillis) throws TimeoutException { 859 synchronized (mLock) { 860 final long startTimeMillis = SystemClock.uptimeMillis(); 861 while (mCallCount < count) { 862 try { 863 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 864 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 865 if (remainingTimeMillis <= 0) { 866 throw new TimeoutException(); 867 } 868 mLock.wait(timeoutMillis); 869 } catch (InterruptedException ie) { 870 /* ignore */ 871 } 872 } 873 } 874 } 875 } 876 877 878 /** 879 * Make {@code printerName} the default printer by adding it to the history of printers by 880 * printing once. 881 * 882 * @param adapter The {@link PrintDocumentAdapter} used 883 * @throws Exception If the printer could not be made default 884 */ makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)885 public void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName) 886 throws Throwable { 887 // Perform a full print operation on the printer 888 Log.d(LOG_TAG, "print"); 889 print(adapter); 890 Log.d(LOG_TAG, "waitForWriteAdapterCallback"); 891 waitForWriteAdapterCallback(1); 892 Log.d(LOG_TAG, "selectPrinter"); 893 selectPrinter(printerName); 894 Log.d(LOG_TAG, "clickPrintButton"); 895 mPrintHelper.submitPrintJob(); 896 897 eventually(() -> { 898 Log.d(LOG_TAG, "answerPrintServicesWarning"); 899 answerPrintServicesWarning(true); 900 Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled"); 901 waitForPrinterDiscoverySessionDestroyCallbackCalled(1); 902 }, OPERATION_TIMEOUT_MILLIS * 2); 903 904 // Switch to new activity, which should now use the default printer 905 Log.d(LOG_TAG, "getActivity().finish()"); 906 getActivity().finish(); 907 908 createActivity(); 909 } 910 911 /** 912 * Annotation used to signal that a test does not need an activity. 913 */ 914 @Retention(RetentionPolicy.RUNTIME) 915 @Target(ElementType.METHOD) 916 protected @interface NoActivity { } 917 918 /** 919 * Rule that handles the {@link NoActivity} annotation. 920 */ 921 private static class ShouldStartActivity implements TestRule { 922 boolean mNoActivity; 923 924 @Override apply(Statement base, org.junit.runner.Description description)925 public Statement apply(Statement base, org.junit.runner.Description description) { 926 for (Annotation annotation : description.getAnnotations()) { 927 if (annotation instanceof NoActivity) { 928 mNoActivity = true; 929 break; 930 } 931 } 932 933 return base; 934 } 935 } 936 } 937