1 /* 2 * Copyright (C) 2015 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 android.print.test.Utils.assertException; 20 import static android.print.test.Utils.eventually; 21 import static android.print.test.Utils.getPrintJob; 22 import static android.print.test.Utils.runOnMainThread; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 29 import android.app.Activity; 30 import android.app.PendingIntent; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.pm.ApplicationInfo; 35 import android.content.pm.PackageInfo; 36 import android.content.pm.PackageManager; 37 import android.graphics.Bitmap; 38 import android.graphics.BitmapFactory; 39 import android.graphics.Canvas; 40 import android.graphics.drawable.Drawable; 41 import android.graphics.drawable.Icon; 42 import android.platform.test.annotations.Presubmit; 43 import android.print.PrintAttributes; 44 import android.print.PrintAttributes.Margins; 45 import android.print.PrintAttributes.MediaSize; 46 import android.print.PrintAttributes.Resolution; 47 import android.print.PrintDocumentAdapter; 48 import android.print.PrintManager; 49 import android.print.PrinterCapabilitiesInfo; 50 import android.print.PrinterId; 51 import android.print.PrinterInfo; 52 import android.print.test.BasePrintTest; 53 import android.print.test.services.FirstPrintService; 54 import android.print.test.services.InfoActivity; 55 import android.print.test.services.PrintServiceCallbacks; 56 import android.print.test.services.PrinterDiscoverySessionCallbacks; 57 import android.print.test.services.SecondPrintService; 58 import android.print.test.services.StubbablePrintService; 59 import android.print.test.services.StubbablePrinterDiscoverySession; 60 import android.printservice.CustomPrinterIconCallback; 61 import android.printservice.PrintJob; 62 import android.printservice.PrintService; 63 64 import androidx.annotation.NonNull; 65 import androidx.test.runner.AndroidJUnit4; 66 67 import org.junit.Test; 68 import org.junit.runner.RunWith; 69 70 import java.util.ArrayList; 71 import java.util.List; 72 73 /** 74 * Test the interface from a print service to the print manager 75 */ 76 @Presubmit 77 @RunWith(AndroidJUnit4.class) 78 public class PrintServicesTest extends BasePrintTest { 79 private static final String PRINTER_NAME = "Test printer"; 80 81 /** The print job processed in the test */ 82 private static PrintJob sPrintJob; 83 84 /** Printer under test */ 85 private static PrinterInfo sPrinter; 86 87 /** The custom printer icon to use */ 88 private Icon mIcon; 89 getDefaultOptionPrinterCapabilites( @onNull PrinterId printerId)90 private @NonNull PrinterCapabilitiesInfo getDefaultOptionPrinterCapabilites( 91 @NonNull PrinterId printerId) { 92 return new PrinterCapabilitiesInfo.Builder(printerId) 93 .setMinMargins(new Margins(200, 200, 200, 200)) 94 .addMediaSize(MediaSize.ISO_A4, true) 95 .addResolution(new Resolution("300x300", "300x300", 300, 300), true) 96 .setColorModes(PrintAttributes.COLOR_MODE_COLOR, 97 PrintAttributes.COLOR_MODE_COLOR).build(); 98 } 99 100 /** 101 * Create a mock {@link PrinterDiscoverySessionCallbacks} that discovers a single printer with 102 * minimal capabilities. 103 * 104 * @return The mock session callbacks 105 */ createMockPrinterDiscoverySessionCallbacks( String printerName)106 private PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 107 String printerName) { 108 return createMockPrinterDiscoverySessionCallbacks(invocation -> { 109 // Get the session. 110 StubbablePrinterDiscoverySession session = 111 ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession(); 112 113 if (session.getPrinters().isEmpty()) { 114 List<PrinterInfo> printers = new ArrayList<>(); 115 116 // Add the printer. 117 PrinterId printerId = session.getService() 118 .generatePrinterId(printerName); 119 120 Intent infoIntent = new Intent(getActivity(), InfoActivity.class); 121 infoIntent.putExtra("PRINTER_NAME", PRINTER_NAME); 122 123 PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 124 0, 125 infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 126 127 sPrinter = new PrinterInfo.Builder(printerId, printerName, 128 PrinterInfo.STATUS_IDLE) 129 .setCapabilities(getDefaultOptionPrinterCapabilites(printerId)) 130 .setDescription("Minimal capabilities") 131 .setInfoIntent(infoPendingIntent) 132 .build(); 133 printers.add(sPrinter); 134 135 session.addPrinters(printers); 136 } 137 138 onPrinterDiscoverySessionCreateCalled(); 139 140 return null; 141 }, null, null, null, invocation -> { 142 CustomPrinterIconCallback callback = (CustomPrinterIconCallback) invocation 143 .getArguments()[2]; 144 145 if (mIcon != null) { 146 callback.onCustomPrinterIconLoaded(mIcon); 147 } 148 return null; 149 }, null, invocation -> { 150 // Take a note onDestroy was called. 151 onPrinterDiscoverySessionDestroyCalled(); 152 return null; 153 }); 154 } 155 156 /** 157 * Get the current progress of #sPrintJob 158 * 159 * @return The current progress 160 * 161 * @throws InterruptedException If the thread was interrupted while setting the progress 162 * @throws Throwable If anything is unexpected. 163 */ getProgress()164 private float getProgress() throws Throwable { 165 float[] printProgress = new float[1]; 166 runOnMainThread(() -> printProgress[0] = sPrintJob.getInfo().getProgress()); 167 168 return printProgress[0]; 169 } 170 171 /** 172 * Get the current status of #sPrintJob 173 * 174 * @return The current status 175 * 176 * @throws InterruptedException If the thread was interrupted while getting the status 177 * @throws Throwable If anything is unexpected. 178 */ getStatus()179 private CharSequence getStatus() throws Throwable { 180 CharSequence[] printStatus = new CharSequence[1]; 181 runOnMainThread(() -> printStatus[0] = sPrintJob.getInfo().getStatus(getActivity() 182 .getPackageManager())); 183 184 return printStatus[0]; 185 } 186 187 /** 188 * Check if a print progress is correct. 189 * 190 * @param desiredProgress The expected @{link PrintProgresses} 191 * 192 * @throws Throwable If anything goes wrong or this takes more than 5 seconds 193 */ checkNotification(float desiredProgress, CharSequence desiredStatus)194 private void checkNotification(float desiredProgress, CharSequence desiredStatus) 195 throws Throwable { 196 eventually(() -> assertEquals(desiredProgress, getProgress(), 0.1)); 197 eventually(() -> assertEquals(desiredStatus.toString(), getStatus().toString())); 198 } 199 200 /** 201 * Set a new progress and status for #sPrintJob 202 * 203 * @param progress The new progress to set 204 * @param status The new status to set 205 * 206 * @throws InterruptedException If the thread was interrupted while setting 207 * @throws Throwable If anything is unexpected. 208 */ setProgressAndStatus(final float progress, final CharSequence status)209 private void setProgressAndStatus(final float progress, final CharSequence status) 210 throws Throwable { 211 runOnMainThread(() -> { 212 sPrintJob.setProgress(progress); 213 sPrintJob.setStatus(status); 214 }); 215 } 216 217 /** 218 * Progress print job and check the print job state. 219 * 220 * @param progress How much to progress 221 * @param status The status to set 222 * 223 * @throws Throwable If anything goes wrong. 224 */ progress(float progress, CharSequence status)225 private void progress(float progress, CharSequence status) throws Throwable { 226 setProgressAndStatus(progress, status); 227 228 // Check that progress of job is correct 229 checkNotification(progress, status); 230 } 231 232 /** 233 * Create mock service callback for a session. 234 * 235 * @param sessionCallbacks The callbacks of the sessopm 236 */ createMockPrinterServiceCallbacks( final PrinterDiscoverySessionCallbacks sessionCallbacks)237 private PrintServiceCallbacks createMockPrinterServiceCallbacks( 238 final PrinterDiscoverySessionCallbacks sessionCallbacks) { 239 return createMockPrintServiceCallbacks( 240 invocation -> sessionCallbacks, 241 invocation -> { 242 sPrintJob = (PrintJob) invocation.getArguments()[0]; 243 sPrintJob.start(); 244 onPrintJobQueuedCalled(); 245 246 return null; 247 }, invocation -> { 248 sPrintJob = (PrintJob) invocation.getArguments()[0]; 249 sPrintJob.cancel(); 250 251 return null; 252 }); 253 } 254 255 /** 256 * Test that the progress and status is propagated correctly. 257 * 258 * @throws Throwable If anything is unexpected. 259 */ 260 @Test 261 public void progress() throws Throwable { 262 // Create the session callbacks that we will be checking. 263 PrinterDiscoverySessionCallbacks sessionCallbacks = 264 createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME); 265 266 // Create the service callbacks for the first print service. 267 PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks( 268 sessionCallbacks); 269 270 // Configure the print services. 271 FirstPrintService.setCallbacks(serviceCallbacks); 272 273 // We don't use the second service, but we have to still configure it 274 SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null)); 275 276 // Create a print adapter that respects the print contract. 277 PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1); 278 279 // Start printing. 280 print(adapter); 281 282 // Wait for write of the first page. 283 waitForWriteAdapterCallback(1); 284 285 // Select the printer. 286 selectPrinter(PRINTER_NAME); 287 288 // Click the print button. 289 mPrintHelper.submitPrintJob(); 290 291 eventually(() -> { 292 // Answer the dialog for the print service cloud warning 293 answerPrintServicesWarning(true); 294 295 // Wait until the print job is queued and #sPrintJob is set 296 waitForServiceOnPrintJobQueuedCallbackCalled(1); 297 }, OPERATION_TIMEOUT_MILLIS * 2); 298 299 // Progress print job and check for appropriate notifications 300 progress(0, "printed 0"); 301 progress(0.5f, "printed 50"); 302 progress(1, "printed 100"); 303 304 // Call complete from the main thread 305 runOnMainThread(sPrintJob::complete); 306 307 // Wait for all print jobs to be handled after which the session destroyed. 308 waitForPrinterDiscoverySessionDestroyCallbackCalled(1); 309 } 310 311 /** 312 * Render a {@link Drawable} into a {@link Bitmap}. 313 * 314 * @param d the drawable to be rendered 315 * 316 * @return the rendered bitmap 317 */ 318 private static Bitmap renderDrawable(Drawable d) { 319 Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(), 320 Bitmap.Config.ARGB_8888); 321 322 Canvas canvas = new Canvas(bitmap); 323 d.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 324 d.draw(canvas); 325 326 return bitmap; 327 } 328 329 /** 330 * Update the printer 331 * 332 * @param sessionCallbacks The callbacks for the service the printer belongs to 333 * @param printer the new printer to use 334 * 335 * @throws InterruptedException If we were interrupted while the printer was updated. 336 * @throws Throwable If anything is unexpected. 337 */ 338 private void updatePrinter(PrinterDiscoverySessionCallbacks sessionCallbacks, 339 final PrinterInfo printer) throws Throwable { 340 runOnMainThread(() -> { 341 ArrayList<PrinterInfo> printers = new ArrayList<>(1); 342 printers.add(printer); 343 sessionCallbacks.getSession().addPrinters(printers); 344 }); 345 346 // Update local copy of printer 347 sPrinter = printer; 348 } 349 350 /** 351 * Assert is the printer icon does not match the bitmap. As the icon update might take some time 352 * we try up to 5 seconds. 353 * 354 * @param bitmap The bitmap to match 355 * 356 * @throws Throwable If anything is unexpected. 357 */ 358 private void assertThatIconIs(Bitmap bitmap) throws Throwable { 359 eventually( 360 () -> assertTrue(bitmap.sameAs(renderDrawable(sPrinter.loadIcon(getActivity()))))); 361 } 362 363 /** 364 * Test that the icon get be updated. 365 * 366 * @throws Throwable If anything is unexpected. 367 */ 368 @Test 369 public void updateIcon() throws Throwable { 370 // Create the session callbacks that we will be checking. 371 final PrinterDiscoverySessionCallbacks sessionCallbacks = 372 createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME); 373 374 // Create the service callbacks for the first print service. 375 PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks( 376 sessionCallbacks); 377 378 // Configure the print services. 379 FirstPrintService.setCallbacks(serviceCallbacks); 380 381 // We don't use the second service, but we have to still configure it 382 SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null)); 383 384 // Create a print adapter that respects the print contract. 385 PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1); 386 387 // Start printing. 388 print(adapter); 389 390 // Open printer selection dropdown list to display icon on screen 391 mPrintHelper.displayPrinterList(); 392 393 // Get the print service's icon 394 PackageManager packageManager = getActivity().getPackageManager(); 395 PackageInfo packageInfo = packageManager.getPackageInfo( 396 new ComponentName(getActivity(), FirstPrintService.class).getPackageName(), 0); 397 ApplicationInfo appInfo = packageInfo.applicationInfo; 398 Drawable printServiceIcon = appInfo.loadIcon(packageManager); 399 400 assertThatIconIs(renderDrawable(printServiceIcon)); 401 402 // Update icon to resource 403 updatePrinter(sessionCallbacks, 404 (new PrinterInfo.Builder(sPrinter)).setIconResourceId(R.drawable.red_printer) 405 .build()); 406 407 assertThatIconIs(renderDrawable(getActivity().getDrawable(R.drawable.red_printer))); 408 409 // Update icon to bitmap 410 Bitmap bm = BitmapFactory.decodeResource(getActivity().getResources(), 411 R.raw.yellow); 412 // Icon will be picked up from the discovery session once setHasCustomPrinterIcon is set 413 mIcon = Icon.createWithBitmap(bm); 414 updatePrinter(sessionCallbacks, 415 (new PrinterInfo.Builder(sPrinter)).setHasCustomPrinterIcon(true).build()); 416 417 assertThatIconIs(renderDrawable(mIcon.loadDrawable(getActivity()))); 418 419 mPrintHelper.closePrinterList(); 420 mPrintHelper.cancelPrinting(); 421 waitForPrinterDiscoverySessionDestroyCallbackCalled(1); 422 } 423 424 /** 425 * Test that we cannot call attachBaseContext 426 * 427 * @throws Throwable If anything is unexpected. 428 */ 429 @Test 430 public void cannotUseAttachBaseContext() throws Throwable { 431 // Create the session callbacks that we will be checking. 432 final PrinterDiscoverySessionCallbacks sessionCallbacks = 433 createMockPrinterDiscoverySessionCallbacks(PRINTER_NAME); 434 435 // Create the service callbacks for the first print service. 436 PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks( 437 sessionCallbacks); 438 439 // Configure the print services. 440 FirstPrintService.setCallbacks(serviceCallbacks); 441 442 // Create a print adapter that respects the print contract. 443 PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1); 444 445 // We don't use the second service, but we have to still configure it 446 SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null)); 447 448 // Start printing to set serviceCallbacks.getService() 449 print(adapter); 450 eventually(() -> assertNotNull(serviceCallbacks.getService())); 451 452 // attachBaseContext should always throw an exception no matter what input value 453 assertException(() -> serviceCallbacks.getService().callAttachBaseContext(null), 454 IllegalStateException.class); 455 assertException(() -> serviceCallbacks.getService().callAttachBaseContext(getActivity()), 456 IllegalStateException.class); 457 458 mPrintHelper.cancelPrinting(); 459 waitForPrinterDiscoverySessionDestroyCallbackCalled(1); 460 } 461 462 /** 463 * Test that the active print jobs can be read 464 * 465 * @throws Throwable If anything is unexpected. 466 */ 467 @Test 468 public void getActivePrintJobs() throws Throwable { 469 clearPrintSpoolerData(); 470 471 try { 472 PrintManager pm = (PrintManager) getActivity().getSystemService(Context.PRINT_SERVICE); 473 474 // Configure first print service 475 PrinterDiscoverySessionCallbacks sessionCallbacks1 = 476 createMockPrinterDiscoverySessionCallbacks("Printer1"); 477 PrintServiceCallbacks serviceCallbacks1 = createMockPrinterServiceCallbacks( 478 sessionCallbacks1); 479 FirstPrintService.setCallbacks(serviceCallbacks1); 480 481 // Configure second print service 482 PrinterDiscoverySessionCallbacks sessionCallbacks2 = 483 createMockPrinterDiscoverySessionCallbacks("Printer2"); 484 PrintServiceCallbacks serviceCallbacks2 = createMockPrinterServiceCallbacks( 485 sessionCallbacks2); 486 SecondPrintService.setCallbacks(serviceCallbacks2); 487 488 // Create a print adapter that respects the print contract. 489 PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1); 490 491 runOnMainThread(() -> pm.print("job1", adapter, null)); 492 493 // Init services 494 waitForPrinterDiscoverySessionCreateCallbackCalled(); 495 496 waitForWriteAdapterCallback(1); 497 selectPrinter("Printer1"); 498 499 StubbablePrintService firstService = serviceCallbacks1.getService(); 500 // Job is not yet confirmed, hence it is not yet "active" 501 runOnMainThread(() -> assertEquals(0, firstService.callGetActivePrintJobs().size())); 502 503 mPrintHelper.submitPrintJob(); 504 505 eventually(() -> { 506 answerPrintServicesWarning(true); 507 waitForServiceOnPrintJobQueuedCallbackCalled(1); 508 }, OPERATION_TIMEOUT_MILLIS * 2); 509 510 eventually(() -> runOnMainThread( 511 () -> assertEquals(1, firstService.callGetActivePrintJobs().size()))); 512 513 // Add another print job to first service 514 resetCounters(); 515 runOnMainThread(() -> pm.print("job2", adapter, null)); 516 waitForWriteAdapterCallback(1); 517 mPrintHelper.submitPrintJob(); 518 waitForServiceOnPrintJobQueuedCallbackCalled(1); 519 520 eventually(() -> runOnMainThread( 521 () -> assertEquals(2, firstService.callGetActivePrintJobs().size()))); 522 523 waitForPrinterDiscoverySessionDestroyCallbackCalled(2); 524 525 // Create print job in second service 526 resetCounters(); 527 runOnMainThread(() -> pm.print("job3", adapter, null)); 528 529 waitForPrinterDiscoverySessionCreateCallbackCalled(); 530 531 waitForWriteAdapterCallback(1); 532 selectPrinter("Printer2"); 533 mPrintHelper.submitPrintJob(); 534 535 StubbablePrintService secondService = serviceCallbacks2.getService(); 536 runOnMainThread(() -> assertEquals(0, secondService.callGetActivePrintJobs().size())); 537 538 eventually(() -> { 539 answerPrintServicesWarning(true); 540 waitForServiceOnPrintJobQueuedCallbackCalled(1); 541 }, OPERATION_TIMEOUT_MILLIS * 2); 542 543 eventually(() -> runOnMainThread( 544 () -> assertEquals(1, secondService.callGetActivePrintJobs().size()))); 545 runOnMainThread(() -> assertEquals(2, firstService.callGetActivePrintJobs().size())); 546 547 // Block last print job. Blocked jobs are still considered active 548 runOnMainThread(() -> sPrintJob.block(null)); 549 eventually(() -> runOnMainThread(() -> assertTrue(sPrintJob.isBlocked()))); 550 runOnMainThread(() -> assertEquals(1, secondService.callGetActivePrintJobs().size())); 551 552 // Fail last print job. Failed job are not active 553 runOnMainThread(() -> sPrintJob.fail(null)); 554 eventually(() -> runOnMainThread(() -> assertTrue(sPrintJob.isFailed()))); 555 runOnMainThread(() -> assertEquals(0, secondService.callGetActivePrintJobs().size())); 556 557 // Cancel job. Canceled jobs are not active 558 runOnMainThread(() -> assertEquals(2, firstService.callGetActivePrintJobs().size())); 559 android.print.PrintJob job2 = getPrintJob(pm, "job2"); 560 runOnMainThread(job2::cancel); 561 eventually(() -> runOnMainThread(() -> assertTrue(job2.isCancelled()))); 562 runOnMainThread(() -> assertEquals(1, firstService.callGetActivePrintJobs().size())); 563 564 waitForPrinterDiscoverySessionDestroyCallbackCalled(2); 565 } finally { 566 clearPrintSpoolerData(); 567 } 568 } 569 570 /** 571 * Test that the icon get be updated. 572 * 573 * @throws Throwable If anything is unexpected. 574 */ 575 @Test 576 public void selectViaInfoIntent() throws Throwable { 577 ArrayList<String> trackedPrinters = new ArrayList<>(); 578 579 // Create the session callbacks that we will be checking. 580 final PrinterDiscoverySessionCallbacks sessionCallbacks = 581 createMockPrinterDiscoverySessionCallbacks(invocation -> { 582 // Get the session. 583 StubbablePrinterDiscoverySession session = 584 ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession(); 585 586 PrinterId printer1Id = session.getService().generatePrinterId("Printer1"); 587 588 PrinterInfo printer1 = new PrinterInfo.Builder(printer1Id, "Printer1", 589 PrinterInfo.STATUS_IDLE).setCapabilities( 590 getDefaultOptionPrinterCapabilites(printer1Id)).build(); 591 592 PrinterId printer2Id = session.getService().generatePrinterId("Printer2"); 593 594 Intent infoIntent = new Intent(getActivity(), InfoActivity.class); 595 infoIntent.putExtra("PRINTER_NAME", "Printer2"); 596 597 PendingIntent infoPendingIntent = PendingIntent.getActivity(getActivity(), 0, 598 infoIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED); 599 600 PrinterInfo printer2 = new PrinterInfo.Builder(printer2Id, "Printer2", 601 PrinterInfo.STATUS_IDLE) 602 .setInfoIntent(infoPendingIntent) 603 .setCapabilities(getDefaultOptionPrinterCapabilites(printer2Id)) 604 .build(); 605 606 List<PrinterInfo> printers = new ArrayList<>(); 607 printers.add(printer1); 608 printers.add(printer2); 609 session.addPrinters(printers); 610 611 onPrinterDiscoverySessionCreateCalled(); 612 613 return null; 614 }, null, null, invocation -> { 615 synchronized (trackedPrinters) { 616 trackedPrinters.add( 617 ((PrinterId) invocation.getArguments()[0]).getLocalId()); 618 trackedPrinters.notifyAll(); 619 } 620 621 return null; 622 }, null, invocation -> { 623 synchronized (trackedPrinters) { 624 trackedPrinters.remove( 625 ((PrinterId) invocation.getArguments()[0]).getLocalId()); 626 trackedPrinters.notifyAll(); 627 } 628 629 return null; 630 }, invocation -> { 631 onPrinterDiscoverySessionDestroyCalled(); 632 return null; 633 }); 634 635 // Create the service callbacks for the first print service. 636 PrintServiceCallbacks serviceCallbacks = createMockPrinterServiceCallbacks( 637 sessionCallbacks); 638 639 // Configure the print services. 640 FirstPrintService.setCallbacks(serviceCallbacks); 641 642 // We don't use the second service, but we have to still configure it 643 SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null)); 644 645 // Create a print adapter that respects the print contract. 646 PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1); 647 648 // Start printing. 649 print(adapter); 650 651 selectPrinter("Printer1"); 652 653 eventually(() -> { 654 synchronized (trackedPrinters) { 655 assertFalse(trackedPrinters.contains("Printer2")); 656 } 657 }); 658 659 // Enter select printer activity 660 selectPrinter("All printers…"); 661 662 try { 663 InfoActivity.addObserver(activity -> { 664 Intent intent = activity.getIntent(); 665 666 assertEquals("Printer2", intent.getStringExtra("PRINTER_NAME")); 667 assertTrue(intent.getBooleanExtra(PrintService.EXTRA_CAN_SELECT_PRINTER, 668 false)); 669 670 activity.setResult(Activity.RESULT_OK, 671 (new Intent()).putExtra(PrintService.EXTRA_SELECT_PRINTER, true)); 672 activity.finish(); 673 }); 674 675 try { 676 // Wait until printer is selected and thereby tracked 677 eventually(() -> { 678 // Open info activity which executes the code above 679 mPrintHelper.displayMoreInfo(); 680 681 eventually(() -> { 682 synchronized (trackedPrinters) { 683 assertTrue(trackedPrinters.contains("Printer2")); 684 } 685 }, OPERATION_TIMEOUT_MILLIS / 2); 686 }, OPERATION_TIMEOUT_MILLIS * 2); 687 } finally { 688 InfoActivity.clearObservers(); 689 } 690 } finally { 691 mPrintHelper.closePrinterList(); 692 } 693 694 mPrintHelper.cancelPrinting(); 695 waitForPrinterDiscoverySessionDestroyCallbackCalled(1); 696 } 697 } 698