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