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