Home | History | Annotate | Download | only in cts
      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 
     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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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         clickPrintButton();
    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             clickPrintButton();
    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             clickPrintButton();
    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             clickPrintButton();
    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