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