1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.print.cts;
18 
19 import static android.print.test.Utils.eventually;
20 import static android.print.test.Utils.runOnMainThread;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.mockito.Mockito.inOrder;
27 
28 import android.print.PrintAttributes;
29 import android.print.PrintAttributes.Margins;
30 import android.print.PrintAttributes.MediaSize;
31 import android.print.PrintAttributes.Resolution;
32 import android.print.PrintDocumentAdapter;
33 import android.print.PrinterCapabilitiesInfo;
34 import android.print.PrinterId;
35 import android.print.PrinterInfo;
36 import android.print.test.BasePrintTest;
37 import android.print.test.services.FirstPrintService;
38 import android.print.test.services.PrintServiceCallbacks;
39 import android.print.test.services.PrinterDiscoverySessionCallbacks;
40 import android.print.test.services.SecondPrintService;
41 import android.print.test.services.StubbablePrinterDiscoverySession;
42 import android.printservice.PrintJob;
43 import android.printservice.PrinterDiscoverySession;
44 import androidx.annotation.NonNull;
45 import android.support.test.runner.AndroidJUnit4;
46 import android.support.test.uiautomator.UiObject;
47 import android.support.test.uiautomator.UiSelector;
48 
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.InOrder;
53 import org.mockito.exceptions.verification.VerificationInOrderFailure;
54 
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.List;
58 
59 /**
60  * This test verifies that the system respects the {@link PrinterDiscoverySession}
61  * contract is respected.
62  */
63 @RunWith(AndroidJUnit4.class)
64 public class PrinterDiscoverySessionLifecycleTest extends BasePrintTest {
65     private static final String FIRST_PRINTER_LOCAL_ID = "first_printer";
66     private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
67 
68     private static StubbablePrinterDiscoverySession sSession;
69 
70     @Before
clearPrintSpoolerState()71     public void clearPrintSpoolerState() throws Exception {
72         clearPrintSpoolerData();
73     }
74 
75     /**
76      * Add a printer to {@#sSession}.
77      *
78      * @param localId The id of the printer to add
79      * @param hasCapabilities If the printer has capabilities
80      */
addPrinter(@onNull String localId, boolean hasCapabilities)81     private void addPrinter(@NonNull String localId, boolean hasCapabilities) {
82         // Add the first printer.
83         PrinterId firstPrinterId = sSession.getService().generatePrinterId(
84                 localId);
85 
86         PrinterInfo.Builder printer = new PrinterInfo.Builder(firstPrinterId,
87                 localId, PrinterInfo.STATUS_IDLE);
88 
89         if (hasCapabilities) {
90             printer.setCapabilities(new PrinterCapabilitiesInfo.Builder(firstPrinterId)
91                     .setMinMargins(new Margins(200, 200, 200, 200))
92                     .addMediaSize(MediaSize.ISO_A0, true)
93                     .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
94                     .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
95                             PrintAttributes.COLOR_MODE_COLOR)
96                     .build());
97         }
98 
99         sSession.addPrinters(Collections.singletonList(printer.build()));
100     }
101 
102     /**
103      * Make {@code localPrinterId} the default printer. This requires a full print workflow.
104      *
105      * As a side-effect also approved the print service.
106      *
107      * @param localPrinterId The printer to make default
108      */
makeDefaultPrinter(String localPrinterId)109     private void makeDefaultPrinter(String localPrinterId) throws Throwable {
110         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
111 
112         print(adapter);
113         waitForWriteAdapterCallback(1);
114 
115         runOnMainThread(() -> addPrinter(localPrinterId, true));
116         selectPrinter(localPrinterId);
117         waitForWriteAdapterCallback(2);
118 
119         clickPrintButton();
120         answerPrintServicesWarning(true);
121 
122         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
123         resetCounters();
124     }
125 
126     /**
127      * Select a printer in the all printers activity
128      *
129      * @param printerName The name of the printer to select
130      */
selectInAllPrintersActivity(@onNull String printerName)131     private void selectInAllPrintersActivity(@NonNull String printerName) throws Exception {
132         while (true) {
133             UiObject printerItem = getUiDevice().findObject(
134                     new UiSelector().text(printerName));
135 
136             if (printerItem.isEnabled()) {
137                 printerItem.click();
138                 break;
139             } else {
140                 Thread.sleep(100);
141             }
142         }
143     }
144 
145     @Test
defaultPrinterBecomesAvailableWhileInBackground()146     public void defaultPrinterBecomesAvailableWhileInBackground() throws Throwable {
147         // Create the session callbacks that we will be checking.
148         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
149                 createMockPrinterDiscoverySessionCallbacks(invocation -> {
150                     sSession =
151                             ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
152 
153                     onPrinterDiscoverySessionCreateCalled();
154                     return null;
155                 }, null, null, null, null, null, invocation -> {
156                     onPrinterDiscoverySessionDestroyCalled();
157                     return null;
158                 });
159 
160         // Create the service callbacks for the first print service.
161         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
162                 invocation -> firstSessionCallbacks, null, null);
163 
164         // Configure the print services.
165         FirstPrintService.setCallbacks(firstServiceCallbacks);
166         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
167 
168         makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
169 
170         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
171         print(adapter);
172         waitForPrinterDiscoverySessionCreateCallbackCalled();
173 
174         waitForPrinterUnavailable();
175 
176         selectPrinter("All printers…");
177         // Let all printers activity start
178         Thread.sleep(500);
179 
180         // Add printer
181         runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
182 
183         // Select printer once available (this returns to main print activity)
184         selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
185 
186         // Wait for preview to load and finish print
187         waitForWriteAdapterCallback(1);
188         clickPrintButton();
189         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
190     }
191 
192     @Test
defaultPrinterBecomesUsableWhileInBackground()193     public void defaultPrinterBecomesUsableWhileInBackground() throws Throwable {
194         // Create the session callbacks that we will be checking.
195         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
196                 createMockPrinterDiscoverySessionCallbacks(invocation -> {
197                     sSession =
198                             ((PrinterDiscoverySessionCallbacks) invocation.getMock()).getSession();
199 
200                     onPrinterDiscoverySessionCreateCalled();
201                     return null;
202                 }, null, null, null, null, null, invocation -> {
203                     onPrinterDiscoverySessionDestroyCalled();
204                     return null;
205                 });
206 
207         // Create the service callbacks for the first print service.
208         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
209                 invocation -> firstSessionCallbacks, null, null);
210 
211         // Configure the print services.
212         FirstPrintService.setCallbacks(firstServiceCallbacks);
213         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
214 
215         makeDefaultPrinter(FIRST_PRINTER_LOCAL_ID);
216 
217         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
218         print(adapter);
219         waitForPrinterDiscoverySessionCreateCallbackCalled();
220 
221         // Add printer but do not enable it (capabilities == null)
222         runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, false));
223         waitForPrinterUnavailable();
224 
225         selectPrinter("All printers…");
226         // Let all printers activity start
227         Thread.sleep(500);
228 
229         // Enable printer
230         runOnMainThread(() -> addPrinter(FIRST_PRINTER_LOCAL_ID, true));
231 
232         // Select printer once available (this returns to main print activity)
233         selectInAllPrintersActivity(FIRST_PRINTER_LOCAL_ID);
234 
235         // Wait for preview to load and finish print
236         waitForWriteAdapterCallback(1);
237         clickPrintButton();
238         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
239     }
240 
241     @Test
normalLifecycle()242     public void normalLifecycle() throws Throwable {
243         // Create the session callbacks that we will be checking.
244         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
245                 createFirstMockPrinterDiscoverySessionCallbacks();
246 
247         // Create the service callbacks for the first print service.
248         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
249                 invocation -> firstSessionCallbacks,
250                 invocation -> {
251                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
252                     // We pretend the job is handled immediately.
253                     printJob.complete();
254                     return null;
255                 }, null);
256 
257         // Configure the print services.
258         FirstPrintService.setCallbacks(firstServiceCallbacks);
259         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
260 
261         // Create a print adapter that respects the print contract.
262         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
263 
264         // Start printing.
265         print(adapter);
266 
267         // Wait for write of the first page.
268         waitForWriteAdapterCallback(1);
269 
270         runOnMainThread(() -> assertFalse(sSession.isDestroyed()));
271         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
272 
273         // Select the first printer.
274         selectPrinter(FIRST_PRINTER_LOCAL_ID);
275 
276         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
277                 sSession.getTrackedPrinters().get(0).getLocalId())));
278         runOnMainThread(() -> assertTrue(sSession.isPrinterDiscoveryStarted()));
279         runOnMainThread(() -> assertEquals(1, sSession.getTrackedPrinters().size()));
280 
281         // Wait for layout as the printer has different capabilities.
282         waitForLayoutAdapterCallbackCount(2);
283 
284         // Select the second printer (same capabilities as the other
285         // one so no layout should happen).
286         selectPrinter(SECOND_PRINTER_LOCAL_ID);
287 
288         eventually(() -> runOnMainThread(() -> assertEquals(SECOND_PRINTER_LOCAL_ID,
289                 sSession.getTrackedPrinters().get(0).getLocalId())));
290         runOnMainThread(() -> assertEquals(1, sSession.getTrackedPrinters().size()));
291 
292         // While the printer discovery session is still alive store the
293         // ids of printers as we want to make some assertions about them
294         // but only the print service can create printer ids which means
295         // that we need to get the created ones.
296         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(
297                 FIRST_PRINTER_LOCAL_ID);
298         PrinterId secondPrinterId = getAddedPrinterIdForLocalId(
299                 SECOND_PRINTER_LOCAL_ID);
300         assertNotNull("Coundn't find printer:" + FIRST_PRINTER_LOCAL_ID, firstPrinterId);
301         assertNotNull("Coundn't find printer:" + SECOND_PRINTER_LOCAL_ID, secondPrinterId);
302 
303         // Click the print button.
304         clickPrintButton();
305 
306         // Answer the dialog for the print service cloud warning
307         answerPrintServicesWarning(true);
308 
309         // Wait for all print jobs to be handled after which the session destroyed.
310         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
311 
312         runOnMainThread(() -> assertTrue(sSession.isDestroyed()));
313         runOnMainThread(() -> assertFalse(sSession.isPrinterDiscoveryStarted()));
314         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
315 
316         // Verify the expected calls.
317         InOrder inOrder = inOrder(firstSessionCallbacks);
318 
319         // We start discovery as the print dialog was up.
320         List<PrinterId> emptyPrinterIdList = Collections.emptyList();
321         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
322                 emptyPrinterIdList);
323 
324         // We selected the first printer and now it should be tracked.
325         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
326                 firstPrinterId);
327 
328         // We selected the second printer so the first should not be tracked.
329         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
330                 firstPrinterId);
331 
332         // We selected the second printer and now it should be tracked.
333         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
334                 secondPrinterId);
335 
336         // The print dialog went away so we first stop the printer tracking...
337         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
338                 secondPrinterId);
339 
340         // ... next we stop printer discovery...
341         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
342 
343         // ... last the session is destroyed.
344         inOrder.verify(firstSessionCallbacks).onDestroy();
345     }
346 
347     @Test
cancelPrintServicesAlertDialog()348     public void cancelPrintServicesAlertDialog() throws Throwable {
349         // Create the session callbacks that we will be checking.
350         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
351                 createFirstMockPrinterDiscoverySessionCallbacks();
352 
353         // Create the service callbacks for the first print service.
354         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
355                 invocation -> firstSessionCallbacks,
356                 invocation -> {
357                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
358                     // We pretend the job is handled immediately.
359                     printJob.complete();
360                     return null;
361                 }, null);
362 
363         // Configure the print services.
364         FirstPrintService.setCallbacks(firstServiceCallbacks);
365         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
366 
367         // Create a print adapter that respects the print contract.
368         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
369 
370         // Start printing.
371         print(adapter);
372 
373         // Wait for write of the first page.
374         waitForWriteAdapterCallback(1);
375 
376         runOnMainThread(() -> assertFalse(sSession.isDestroyed()));
377         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
378 
379         // Select the first printer.
380         selectPrinter(FIRST_PRINTER_LOCAL_ID);
381 
382         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
383                 sSession.getTrackedPrinters().get(0).getLocalId())));
384         runOnMainThread(() -> assertTrue(sSession.isPrinterDiscoveryStarted()));
385         runOnMainThread(() -> assertEquals(1, sSession.getTrackedPrinters().size()));
386 
387         // While the printer discovery session is still alive store the
388         // ids of printers as we want to make some assertions about them
389         // but only the print service can create printer ids which means
390         // that we need to get the created ones.
391         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(
392                 FIRST_PRINTER_LOCAL_ID);
393         assertNotNull("Coundn't find printer:" + FIRST_PRINTER_LOCAL_ID, firstPrinterId);
394 
395         // Click the print button.
396         clickPrintButton();
397 
398         // Cancel the dialog for the print service cloud warning
399         answerPrintServicesWarning(false);
400 
401         // Click the print button again.
402         clickPrintButton();
403 
404         // Answer the dialog for the print service cloud warning
405         answerPrintServicesWarning(true);
406 
407         // Wait for all print jobs to be handled after which the session destroyed.
408         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
409 
410         runOnMainThread(() -> assertTrue(sSession.isDestroyed()));
411         runOnMainThread(() -> assertFalse(sSession.isPrinterDiscoveryStarted()));
412         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
413 
414         // Verify the expected calls.
415         InOrder inOrder = inOrder(firstSessionCallbacks);
416 
417         // We start discovery as the print dialog was up.
418         List<PrinterId> emptyPrinterIdList = Collections.emptyList();
419         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
420                 emptyPrinterIdList);
421 
422         // We selected the first printer and now it should be tracked.
423         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
424                 firstPrinterId);
425 
426         // We selected the second printer so the first should not be tracked.
427         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
428                 firstPrinterId);
429 
430         // ... next we stop printer discovery...
431         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
432 
433         // ... last the session is destroyed.
434         inOrder.verify(firstSessionCallbacks).onDestroy();
435     }
436 
437     @Test
startPrinterDiscoveryWithHistoricalPrinters()438     public void startPrinterDiscoveryWithHistoricalPrinters() throws Throwable {
439         // Create the session callbacks that we will be checking.
440         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
441                 createFirstMockPrinterDiscoverySessionCallbacks();
442 
443         // Create the service callbacks for the first print service.
444         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
445                 invocation -> firstSessionCallbacks,
446                 invocation -> {
447                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
448                     // We pretend the job is handled immediately.
449                     printJob.complete();
450                     return null;
451                 }, null);
452 
453         // Configure the print services.
454         FirstPrintService.setCallbacks(firstServiceCallbacks);
455         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
456 
457         // Create a print adapter that respects the print contract.
458         PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
459 
460         // Start printing.
461         print(adapter);
462 
463         // Wait for write of the first page.
464         waitForWriteAdapterCallback(1);
465 
466         runOnMainThread(() -> assertFalse(sSession.isDestroyed()));
467         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
468 
469         // Select the first printer.
470         selectPrinter(FIRST_PRINTER_LOCAL_ID);
471 
472         eventually(() -> runOnMainThread(() -> assertEquals(FIRST_PRINTER_LOCAL_ID,
473                 sSession.getTrackedPrinters().get(0).getLocalId())));
474         runOnMainThread(() -> assertTrue(sSession.isPrinterDiscoveryStarted()));
475         runOnMainThread(() -> assertEquals(1, sSession.getTrackedPrinters().size()));
476 
477         // Wait for a layout to finish - first layout was for the
478         // PDF printer, second for the first printer in preview mode.
479         waitForLayoutAdapterCallbackCount(2);
480 
481         // While the printer discovery session is still alive store the
482         // ids of printer as we want to make some assertions about it
483         // but only the print service can create printer ids which means
484         // that we need to get the created one.
485         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(
486                 FIRST_PRINTER_LOCAL_ID);
487 
488         // Click the print button.
489         clickPrintButton();
490 
491         // Answer the dialog for the print service cloud warning
492         answerPrintServicesWarning(true);
493 
494         // Wait for the print to complete.
495         waitForAdapterFinishCallbackCalled();
496 
497         // Now print again as we want to confirm that the start
498         // printer discovery passes in the priority list.
499         print(adapter);
500 
501         // Wait for a layout to finish - first layout was for the
502         // PDF printer, second for the first printer in preview mode,
503         // the third for the first printer in non-preview mode, and
504         // now a fourth for the PDF printer as we are printing again.
505         waitForLayoutAdapterCallbackCount(4);
506 
507         // Cancel the printing.
508         getUiDevice().pressBack(); // wakes up the device.
509         getUiDevice().pressBack();
510 
511         // Wait for all print jobs to be handled after which the is session destroyed.
512         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
513 
514         runOnMainThread(() -> assertTrue(sSession.isDestroyed()));
515         runOnMainThread(() -> assertFalse(sSession.isPrinterDiscoveryStarted()));
516         runOnMainThread(() -> assertEquals(0, sSession.getTrackedPrinters().size()));
517 
518         // Verify the expected calls.
519         InOrder inOrder = inOrder(firstSessionCallbacks);
520 
521         // We start discovery with no printer history.
522         List<PrinterId> priorityList = new ArrayList<>();
523         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
524                 priorityList);
525 
526         // We selected the first printer and now it should be tracked.
527         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
528                 firstPrinterId);
529 
530         // We confirmed print so the first should not be tracked.
531         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
532                 firstPrinterId);
533 
534         // This is tricky. It is possible that the print activity was not
535         // destroyed (the platform delays destruction at convenient time as
536         // an optimization) and we get the same instance which means that
537         // the discovery session may not have been destroyed. We try the
538         // case with the activity being destroyed and if this fails the
539         // case with the activity brought to front.
540         priorityList.add(firstPrinterId);
541         try {
542             inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(priorityList);
543         } catch (VerificationInOrderFailure error) {
544             inOrder.verify(firstSessionCallbacks).onValidatePrinters(priorityList);
545         }
546 
547         // The system selects the highest ranked historical printer.
548         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
549                 firstPrinterId);
550 
551         // We canceled print so the first should not be tracked.
552         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
553                 firstPrinterId);
554 
555 
556         // Discovery is always stopped before the session is always destroyed.
557         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
558 
559         // ...last the session is destroyed.
560         inOrder.verify(firstSessionCallbacks).onDestroy();
561     }
562 
563     @Test
addRemovePrinters()564     public void addRemovePrinters() throws Throwable {
565         StubbablePrinterDiscoverySession[] session = new StubbablePrinterDiscoverySession[1];
566 
567         // Create the session callbacks that we will be checking.
568         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
569                 createMockPrinterDiscoverySessionCallbacks(invocation -> {
570                     session[0] = ((PrinterDiscoverySessionCallbacks)
571                             invocation.getMock()).getSession();
572 
573                     onPrinterDiscoverySessionCreateCalled();
574                     return null;
575                 }, null, null, null, null, null, invocation -> {
576                     onPrinterDiscoverySessionDestroyCalled();
577                     return null;
578                 });
579 
580         // Create the service callbacks for the first print service.
581         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
582                 invocation -> firstSessionCallbacks, null, null);
583 
584         // Configure the print services.
585         FirstPrintService.setCallbacks(firstServiceCallbacks);
586         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
587 
588         print(createDefaultPrintDocumentAdapter(1));
589 
590         waitForPrinterDiscoverySessionCreateCallbackCalled();
591 
592         runOnMainThread(() -> assertEquals(0, session[0].getPrinters().size()));
593 
594         PrinterId[] printerIds = new PrinterId[3];
595         runOnMainThread(() -> {
596             printerIds[0] = session[0].getService().generatePrinterId("0");
597             printerIds[1] = session[0].getService().generatePrinterId("1");
598             printerIds[2] = session[0].getService().generatePrinterId("2");
599         });
600 
601         PrinterInfo printer1 = (new PrinterInfo.Builder(printerIds[0], "0",
602                 PrinterInfo.STATUS_IDLE)).build();
603 
604         PrinterInfo printer2 = (new PrinterInfo.Builder(printerIds[1], "1",
605                 PrinterInfo.STATUS_IDLE)).build();
606 
607         PrinterInfo printer3 = (new PrinterInfo.Builder(printerIds[2], "2",
608                 PrinterInfo.STATUS_IDLE)).build();
609 
610         ArrayList<PrinterInfo> printers = new ArrayList<>();
611         printers.add(printer1);
612         runOnMainThread(() -> session[0].addPrinters(printers));
613         eventually(() -> runOnMainThread(() -> assertEquals(1, session[0].getPrinters().size())));
614 
615         printers.add(printer2);
616         printers.add(printer3);
617         runOnMainThread(() -> session[0].addPrinters(printers));
618         eventually(() -> runOnMainThread(() -> assertEquals(3, session[0].getPrinters().size())));
619 
620         ArrayList<PrinterId> printerIdsToRemove = new ArrayList<>();
621         printerIdsToRemove.add(printer1.getId());
622         runOnMainThread(() -> session[0].removePrinters(printerIdsToRemove));
623         eventually(() -> runOnMainThread(() -> assertEquals(2, session[0].getPrinters().size())));
624 
625         printerIdsToRemove.add(printer2.getId());
626         printerIdsToRemove.add(printer3.getId());
627         runOnMainThread(() -> session[0].removePrinters(printerIdsToRemove));
628         eventually(() -> runOnMainThread(() -> assertEquals(0, session[0].getPrinters().size())));
629 
630         getUiDevice().pressBack();
631 
632         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
633     }
634 
getAddedPrinterIdForLocalId(String printerLocalId)635     private PrinterId getAddedPrinterIdForLocalId(String printerLocalId) throws Throwable {
636         final List<PrinterInfo> reportedPrinters = new ArrayList<>();
637         runOnMainThread(() -> {
638             // Grab the printer ids as only the service can create such.
639             reportedPrinters.addAll(sSession.getPrinters());
640         });
641 
642         final int reportedPrinterCount = reportedPrinters.size();
643         for (int i = 0; i < reportedPrinterCount; i++) {
644             PrinterInfo reportedPrinter = reportedPrinters.get(i);
645             String localId = reportedPrinter.getId().getLocalId();
646             if (printerLocalId.equals(localId)) {
647                 return reportedPrinter.getId();
648             }
649         }
650 
651         return null;
652     }
653 
createSecondMockPrintServiceCallbacks()654     private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
655         return createMockPrintServiceCallbacks(null, null, null);
656     }
657 
createFirstMockPrinterDiscoverySessionCallbacks()658     private PrinterDiscoverySessionCallbacks createFirstMockPrinterDiscoverySessionCallbacks() {
659         return createMockPrinterDiscoverySessionCallbacks(invocation -> {
660             // Get the session.
661             sSession = ((PrinterDiscoverySessionCallbacks)
662                     invocation.getMock()).getSession();
663 
664             assertTrue(sSession.isPrinterDiscoveryStarted());
665 
666             addPrinter(FIRST_PRINTER_LOCAL_ID, false);
667             addPrinter(SECOND_PRINTER_LOCAL_ID, false);
668 
669             return null;
670         }, invocation -> {
671             assertFalse(sSession.isPrinterDiscoveryStarted());
672             return null;
673         }, null, invocation -> {
674             // Get the session.
675             StubbablePrinterDiscoverySession session = ((PrinterDiscoverySessionCallbacks)
676                     invocation.getMock()).getSession();
677 
678             PrinterId trackedPrinterId = (PrinterId) invocation.getArguments()[0];
679             List<PrinterInfo> reportedPrinters = session.getPrinters();
680 
681             // We should be tracking a printer that we added.
682             PrinterInfo trackedPrinter = null;
683             final int reportedPrinterCount = reportedPrinters.size();
684             for (int i = 0; i < reportedPrinterCount; i++) {
685                 PrinterInfo reportedPrinter = reportedPrinters.get(i);
686                 if (reportedPrinter.getId().equals(trackedPrinterId)) {
687                     trackedPrinter = reportedPrinter;
688                     break;
689                 }
690             }
691             assertNotNull("Can track only added printers", trackedPrinter);
692 
693             assertTrue(sSession.getTrackedPrinters().contains(trackedPrinter.getId()));
694             assertEquals(1, sSession.getTrackedPrinters().size());
695 
696             // If the printer does not have capabilities reported add them.
697             if (trackedPrinter.getCapabilities() == null) {
698 
699                 // Add the capabilities to emulate lazy discovery.
700                 // Same for each printer is fine for what we test.
701                 PrinterCapabilitiesInfo capabilities =
702                         new PrinterCapabilitiesInfo.Builder(trackedPrinterId)
703                                 .setMinMargins(new Margins(200, 200, 200, 200))
704                                 .addMediaSize(MediaSize.ISO_A4, true)
705                                 .addMediaSize(MediaSize.ISO_A5, false)
706                                 .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
707                                 .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
708                                         PrintAttributes.COLOR_MODE_COLOR)
709                                 .build();
710                 PrinterInfo updatedPrinter = new PrinterInfo.Builder(trackedPrinter)
711                         .setCapabilities(capabilities)
712                         .build();
713 
714                 // Update the printer.
715                 List<PrinterInfo> printers = new ArrayList<>();
716                 printers.add(updatedPrinter);
717                 session.addPrinters(printers);
718             }
719 
720             return null;
721         }, null, null, invocation -> {
722             assertTrue(sSession.isDestroyed());
723 
724             // Take a note onDestroy was called.
725             onPrinterDiscoverySessionDestroyCalled();
726             return null;
727         });
728     }
729 }
730