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