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