1 /*
2  * Copyright (C) 2016 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 android.os.ParcelFileDescriptor;
20 import android.print.PageRange;
21 import android.print.PrintAttributes;
22 import android.print.PrintAttributes.Margins;
23 import android.print.PrintAttributes.MediaSize;
24 import android.print.PrintAttributes.Resolution;
25 import android.print.PrintDocumentAdapter;
26 import android.print.PrintDocumentAdapter.LayoutResultCallback;
27 import android.print.PrintDocumentAdapter.WriteResultCallback;
28 import android.print.PrintDocumentInfo;
29 import android.print.PrinterCapabilitiesInfo;
30 import android.print.PrinterId;
31 import android.print.PrinterInfo;
32 import android.print.cts.services.FirstPrintService;
33 import android.print.cts.services.PrintServiceCallbacks;
34 import android.print.cts.services.PrinterDiscoverySessionCallbacks;
35 import android.print.cts.services.SecondPrintService;
36 import android.print.cts.services.StubbablePrinterDiscoverySession;
37 import android.util.Log;
38 import android.support.test.uiautomator.UiObject;
39 import android.support.test.uiautomator.UiSelector;
40 import org.mockito.invocation.InvocationOnMock;
41 import org.mockito.stubbing.Answer;
42 
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.concurrent.TimeoutException;
46 import java.util.function.Consumer;
47 import java.util.function.Function;
48 
49 /**
50  * This test verifies changes to the printer capabilities are applied correctly.
51  */
52 public class PrinterCapabilitiesTest extends BasePrintTest {
53     private static final String LOG_TAG = "PrinterCapabilitiesTest";
54     private static final String PRINTER_NAME = "Test printer";
55 
56     private static final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
57     private static final PrintAttributes.Resolution RESOLUTION_300 =
58             new PrintAttributes.Resolution("300", "300", 300, 300);
59     private static final PrintAttributes.Resolution RESOLUTION_600 =
60             new PrintAttributes.Resolution("600", "600", 600, 600);
61 
62     /**
63      * Generate a new list of printers containing a singer printer with the given media size and
64      * status. The other capabilities are default values.
65      *
66      * @param printerId The id of the printer
67      * @param mediaSize The media size to use
68      * @param status    The status of th printer
69      *
70      * @return The list of printers
71      */
generatePrinters(PrinterId printerId, MediaSize mediaSize, int status)72     private List<PrinterInfo> generatePrinters(PrinterId printerId, MediaSize mediaSize,
73             int status) {
74         List<PrinterInfo> printers = new ArrayList<>(1);
75 
76         PrinterCapabilitiesInfo cap;
77 
78         if (mediaSize != null) {
79             PrinterCapabilitiesInfo.Builder builder = new PrinterCapabilitiesInfo.Builder(
80                     printerId);
81             builder.setMinMargins(DEFAULT_MARGINS)
82                     .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
83                             PrintAttributes.COLOR_MODE_COLOR)
84                     .setDuplexModes(PrintAttributes.DUPLEX_MODE_NONE,
85                             PrintAttributes.DUPLEX_MODE_NONE)
86                     .addMediaSize(mediaSize, true)
87                     .addResolution(RESOLUTION_300, true);
88             cap = builder.build();
89         } else {
90             cap = null;
91         }
92 
93         printers.add(new PrinterInfo.Builder(printerId, PRINTER_NAME, status).setCapabilities(cap)
94                 .build());
95 
96         return printers;
97     }
98 
99     /**
100      * Wait until the print activity requested an update with print attributes matching the media
101      * size.
102      *
103      * @param printAttributes The print attributes container
104      * @param mediaSize       The media size to match
105      *
106      * @throws Exception If anything unexpected happened, e.g. the attributes did not change.
107      */
waitForMediaSizeChange(PrintAttributes[] printAttributes, MediaSize mediaSize)108     private void waitForMediaSizeChange(PrintAttributes[] printAttributes, MediaSize mediaSize)
109             throws Exception {
110         synchronized (PrinterCapabilitiesTest.this) {
111             long endTime = System.currentTimeMillis() + OPERATION_TIMEOUT_MILLIS;
112             while (printAttributes[0] == null ||
113                     !printAttributes[0].getMediaSize().equals(mediaSize)) {
114                 wait(Math.max(0, endTime - System.currentTimeMillis()));
115 
116                 if (endTime < System.currentTimeMillis()) {
117                     throw new TimeoutException(
118                             "Print attributes did not change to " + mediaSize + " in " +
119                                     OPERATION_TIMEOUT_MILLIS + " ms. Current attributes"
120                                     + printAttributes[0]);
121                 }
122             }
123         }
124     }
125 
126     /**
127      * Change the media size of the capabilities of the printer
128      *
129      * @param session     The session used in the test
130      * @param printerId   The printer to change
131      * @param mediaSize   The new mediaSize to apply
132      * @param isAvailable If the printer should be available or not
133      */
changeCapabilities(final StubbablePrinterDiscoverySession session, final PrinterId printerId, final MediaSize mediaSize, final boolean isAvailable)134     private void changeCapabilities(final StubbablePrinterDiscoverySession session,
135             final PrinterId printerId, final MediaSize mediaSize, final boolean isAvailable) {
136         getInstrumentation().runOnMainSync(new Runnable() {
137             @Override
138             public void run() {
139                 session.addPrinters(generatePrinters(printerId, mediaSize, isAvailable ?
140                         PrinterInfo.STATUS_IDLE :
141                         PrinterInfo.STATUS_UNAVAILABLE));
142             }
143         });
144     }
145 
146     /**
147      * Wait until the message is shown that indicates that a printer is unavilable.
148      *
149      * @throws Exception If anything was unexpected.
150      */
waitForPrinterUnavailable()151     private void waitForPrinterUnavailable() throws Exception {
152         final String PRINTER_UNAVAILABLE_MSG = "This printer isn't available right now.";
153 
154         UiObject message = getUiDevice().findObject(new UiSelector().resourceId(
155                 "com.android.printspooler:id/message"));
156         if (!message.getText().equals(PRINTER_UNAVAILABLE_MSG)) {
157             throw new Exception("Wrong message: " + message.getText() + " instead of " +
158                     PRINTER_UNAVAILABLE_MSG);
159         }
160     }
161 
162     /**
163      * A single test case from {@link #testPrinterCapabilityChange}. Tests a single capability
164      * change.
165      *
166      * @param session     The session used in  the test
167      * @param printerId   The ID of the test printer
168      * @param availBefore If the printer should be available before the change
169      * @param msBefore    The media size of the printer before the change
170      * @param availAfter  If the printer should be available after the change
171      * @param msAfter     The media size of the printer after the change
172      *
173      * @throws Exception If anything is unexpected
174      */
testCase(final StubbablePrinterDiscoverySession[] session, final PrinterId[] printerId, final boolean availBefore, final MediaSize msBefore, final boolean availAfter, final MediaSize msAfter)175     private void testCase(final StubbablePrinterDiscoverySession[] session,
176             final PrinterId[] printerId, final boolean availBefore, final MediaSize msBefore,
177             final boolean availAfter, final MediaSize msAfter) throws Exception {
178         if (!supportsPrinting()) {
179             return;
180         }
181 
182         Log.i(LOG_TAG, "Test case: " + availBefore + ", " + msBefore + " -> " + availAfter + ", "
183                 + msAfter);
184 
185         final PrintAttributes[] layoutAttributes = new PrintAttributes[1];
186         final PrintAttributes[] writeAttributes = new PrintAttributes[1];
187 
188         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
189                 new Answer<Void>() {
190                     @Override
191                     public Void answer(InvocationOnMock invocation) throws Throwable {
192                         LayoutResultCallback callback = (LayoutResultCallback) invocation
193                                 .getArguments()[3];
194                         PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
195                                 .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
196                                 .setPageCount(1)
197                                 .build();
198 
199                         synchronized (PrinterCapabilitiesTest.this) {
200                             layoutAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
201 
202                             PrinterCapabilitiesTest.this.notify();
203                         }
204 
205                         callback.onLayoutFinished(info, true);
206                         return null;
207                     }
208                 },
209                 new Answer<Void>() {
210                     @Override
211                     public Void answer(InvocationOnMock invocation) throws Throwable {
212                         Object[] args = invocation.getArguments();
213                         PageRange[] pages = (PageRange[]) args[0];
214                         ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
215                         WriteResultCallback callback = (WriteResultCallback) args[3];
216 
217                         writeBlankPages(layoutAttributes[0], fd, pages[0].getStart(),
218                                 pages[0].getEnd());
219                         fd.close();
220 
221                         synchronized (PrinterCapabilitiesTest.this) {
222                             writeAttributes[0] = layoutAttributes[0];
223 
224                             PrinterCapabilitiesTest.this.notify();
225                         }
226 
227                         callback.onWriteFinished(pages);
228                         return null;
229                     }
230                 }, null);
231 
232         // Start printing.
233         print(adapter);
234 
235         // Select the printer.
236         selectPrinter(PRINTER_NAME);
237 
238         changeCapabilities(session[0], printerId[0], msBefore, availBefore);
239         if (availBefore && msBefore != null) {
240             waitForMediaSizeChange(layoutAttributes, msBefore);
241             waitForMediaSizeChange(writeAttributes, msBefore);
242         } else {
243             waitForPrinterUnavailable();
244         }
245 
246         changeCapabilities(session[0], printerId[0], msAfter, availAfter);
247         if (availAfter && msAfter != null) {
248             waitForMediaSizeChange(layoutAttributes, msAfter);
249             waitForMediaSizeChange(writeAttributes, msAfter);
250         } else {
251             waitForPrinterUnavailable();
252         }
253 
254         // Reset printer to default in case discovery session is reused
255         changeCapabilities(session[0], printerId[0], MediaSize.NA_LETTER, true);
256         waitForMediaSizeChange(layoutAttributes, MediaSize.NA_LETTER);
257         waitForMediaSizeChange(writeAttributes, MediaSize.NA_LETTER);
258 
259         getUiDevice().pressBack();
260 
261         // Wait until PrintActivity is gone
262         Thread.sleep(500);
263     }
264 
265     /**
266      * Tests that the printActivity propertly requests (layout and write) updates when the printer
267      * capabilities change. This tests all combination of changes.
268      *
269      * @throws Exception If something is unexpected.
270      */
testPrinterCapabilityChange()271     public void testPrinterCapabilityChange() throws Exception {
272         // The session might be reused between test cases, hence carry them from test case to case
273         final StubbablePrinterDiscoverySession[] session = new StubbablePrinterDiscoverySession[1];
274         final PrinterId[] printerId = new PrinterId[1];
275 
276         // Create the session[0] callbacks that we will be checking.
277         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
278                 createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
279                     @Override
280                     public Void answer(InvocationOnMock invocation) {
281                         session[0] = ((PrinterDiscoverySessionCallbacks) invocation.getMock())
282                                 .getSession();
283 
284                         printerId[0] = session[0].getService().generatePrinterId(PRINTER_NAME);
285 
286                         session[0].addPrinters(generatePrinters(printerId[0], MediaSize.NA_LETTER,
287                                 PrinterInfo.STATUS_IDLE));
288                         return null;
289                     }
290                 }, null, null, null, null, null, new Answer<Void>() {
291                     @Override
292                     public Void answer(InvocationOnMock invocation) {
293                         onPrinterDiscoverySessionDestroyCalled();
294                         return null;
295                     }
296                 });
297 
298         // Create the service callbacks for the first print service.
299         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
300                 new Answer<PrinterDiscoverySessionCallbacks>() {
301                     @Override
302                     public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
303                         return firstSessionCallbacks;
304                     }
305                 }, null, null);
306 
307         // Configure the print services.
308         FirstPrintService.setCallbacks(firstServiceCallbacks);
309         SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
310 
311         testCase(session, printerId, false, null, false, null);
312         testCase(session, printerId, false, null, false, MediaSize.ISO_A0);
313         testCase(session, printerId, false, null, false, MediaSize.ISO_B0);
314         testCase(session, printerId, false, null, true, null);
315         testCase(session, printerId, false, null, true, MediaSize.ISO_A0);
316         testCase(session, printerId, false, null, true, MediaSize.ISO_B0);
317         testCase(session, printerId, false, MediaSize.ISO_A0, false, null);
318         testCase(session, printerId, false, MediaSize.ISO_A0, false, MediaSize.ISO_A0);
319         testCase(session, printerId, false, MediaSize.ISO_A0, false, MediaSize.ISO_B0);
320         testCase(session, printerId, false, MediaSize.ISO_A0, true, null);
321         testCase(session, printerId, false, MediaSize.ISO_A0, true, MediaSize.ISO_A0);
322         testCase(session, printerId, false, MediaSize.ISO_A0, true, MediaSize.ISO_B0);
323         testCase(session, printerId, true, null, false, null);
324         testCase(session, printerId, true, null, false, MediaSize.ISO_B0);
325         testCase(session, printerId, true, null, true, null);
326         testCase(session, printerId, true, null, true, MediaSize.ISO_B0);
327         testCase(session, printerId, true, MediaSize.ISO_A0, false, null);
328         testCase(session, printerId, true, MediaSize.ISO_A0, false, MediaSize.ISO_A0);
329         testCase(session, printerId, true, MediaSize.ISO_A0, false, MediaSize.ISO_B0);
330         testCase(session, printerId, true, MediaSize.ISO_A0, true, null);
331         testCase(session, printerId, true, MediaSize.ISO_A0, true, MediaSize.ISO_A0);
332         testCase(session, printerId, true, MediaSize.ISO_A0, true, MediaSize.ISO_B0);
333 
334         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
335     }
336 
337     /**
338      * Run a runnable and expect and exception of a certain type.
339      *
340      * @param r The runnable to run
341      * @param expectedClass The expected exception type
342      */
assertException(Runnable r, Class<? extends RuntimeException> expectedClass)343     private void assertException(Runnable r, Class<? extends RuntimeException> expectedClass) {
344         try {
345             r.run();
346         } catch (Exception e) {
347             if (e.getClass().isAssignableFrom(expectedClass)) {
348                 return;
349             } else {
350                 throw new AssertionError("Expected: " + expectedClass.getName() + ", got: "
351                         + e.getClass().getName());
352             }
353         }
354 
355         throw new AssertionError("No exception thrown");
356     }
357 
358     /**
359      * That that you cannot create illegal PrinterCapabilityInfos.
360      *
361      * @throws Exception If anything is unexpected
362      */
testIllegalPrinterCapabilityInfos()363     public void testIllegalPrinterCapabilityInfos() throws Exception {
364         if (!supportsPrinting()) {
365             return;
366         }
367 
368         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
369                 createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
370                     @Override
371                     public Void answer(InvocationOnMock invocation) {
372                         StubbablePrinterDiscoverySession session =
373                                 ((PrinterDiscoverySessionCallbacks)
374                                         invocation.getMock()).getSession();
375 
376                         PrinterId printerId = session.getService().generatePrinterId(PRINTER_NAME);
377 
378                         // printerId need to be set
379                         assertException(() -> new PrinterCapabilitiesInfo.Builder(null),
380                                 IllegalArgumentException.class);
381 
382                         // All capability fields (beside duplex) need to be initialized:
383                         // Test no color
384                         assertException(() ->
385                                         (new PrinterCapabilitiesInfo.Builder(printerId))
386                                                 .setMinMargins(DEFAULT_MARGINS)
387                                                 .addMediaSize(MediaSize.ISO_A4, true)
388                                                 .addResolution(RESOLUTION_300, true).build(),
389                                 IllegalStateException.class);
390                         // Test bad colors
391                         assertException(() ->
392                                         (new PrinterCapabilitiesInfo.Builder(printerId))
393                                                 .setColorModes(0xffff,
394                                                         PrintAttributes.COLOR_MODE_MONOCHROME),
395                                 IllegalArgumentException.class);
396                         // Test bad duplex mode
397                         assertException(() ->
398                                         (new PrinterCapabilitiesInfo.Builder(printerId))
399                                                 .setDuplexModes(0xffff,
400                                                         PrintAttributes.DUPLEX_MODE_NONE),
401                                 IllegalArgumentException.class);
402                         // Test no mediasize
403                         assertException(() ->
404                                         (new PrinterCapabilitiesInfo.Builder(printerId))
405                                                 .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
406                                                         PrintAttributes.COLOR_MODE_COLOR)
407                                                 .setMinMargins(DEFAULT_MARGINS)
408                                                 .addResolution(RESOLUTION_300, true).build(),
409                                 IllegalStateException.class);
410                         // Test no default mediasize
411                         assertException(() ->
412                                         (new PrinterCapabilitiesInfo.Builder(printerId))
413                                                 .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
414                                                         PrintAttributes.COLOR_MODE_COLOR)
415                                                 .setMinMargins(DEFAULT_MARGINS)
416                                                 .addMediaSize(MediaSize.ISO_A4, false)
417                                                 .addResolution(RESOLUTION_300, true).build(),
418                                 IllegalStateException.class);
419                         // Test two default mediasizes
420                         assertException(() ->
421                                         (new PrinterCapabilitiesInfo.Builder(printerId))
422                                                 .addMediaSize(MediaSize.ISO_A4, true)
423                                                 .addMediaSize(MediaSize.ISO_A5, true),
424                                 IllegalArgumentException.class);
425                         // Test no resolution
426                         assertException(() ->
427                                         (new PrinterCapabilitiesInfo.Builder(printerId))
428                                                 .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
429                                                         PrintAttributes.COLOR_MODE_COLOR)
430                                                 .setMinMargins(DEFAULT_MARGINS)
431                                                 .addMediaSize(MediaSize.ISO_A4, true).build(),
432                                 IllegalStateException.class);
433                         // Test no default resolution
434                         assertException(() ->
435                                         (new PrinterCapabilitiesInfo.Builder(printerId))
436                                                 .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
437                                                         PrintAttributes.COLOR_MODE_COLOR)
438                                                 .setMinMargins(DEFAULT_MARGINS)
439                                                 .addMediaSize(MediaSize.ISO_A4, true)
440                                                 .addResolution(RESOLUTION_300, false).build(),
441                                 IllegalStateException.class);
442                         // Test two default resolutions
443                         assertException(() ->
444                                         (new PrinterCapabilitiesInfo.Builder(printerId))
445                                                 .addResolution(RESOLUTION_300, true)
446                                                 .addResolution(RESOLUTION_600, true),
447                                 IllegalArgumentException.class);
448 
449                         onPrinterDiscoverySessionCreateCalled();
450                         return null;
451                     }
452                 }, null, null, null, null, null, new Answer<Void>() {
453                     @Override
454                     public Void answer(InvocationOnMock invocation) {
455                         onPrinterDiscoverySessionDestroyCalled();
456                         return null;
457                     }
458                 });
459 
460         // Create the service callbacks for the first print service.
461         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
462                 new Answer<PrinterDiscoverySessionCallbacks>() {
463                     @Override
464                     public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
465                         return firstSessionCallbacks;
466                     }
467                 }, null, null);
468 
469         // Configure the print services.
470         FirstPrintService.setCallbacks(firstServiceCallbacks);
471         SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
472 
473         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(null, null, null);
474 
475         // Start printing.
476         print(adapter);
477 
478         waitForPrinterDiscoverySessionCreateCallbackCalled();
479 
480         getActivity().finish();
481 
482         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
483     }
484 
485     /**
486      * That that you can use all sane legal PrinterCapabilityInfos.
487      *
488      * @throws Exception If anything is unexpected
489      */
testSanePrinterCapabilityInfos()490     public void testSanePrinterCapabilityInfos() throws Exception {
491         if (!supportsPrinting()) {
492             return;
493         }
494 
495         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
496                 createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
497                     @Override
498                     public Void answer(InvocationOnMock invocation) {
499                         StubbablePrinterDiscoverySession session =
500                                 ((PrinterDiscoverySessionCallbacks)
501                                         invocation.getMock()).getSession();
502 
503                         MediaSize[] mediaSizes = {MediaSize.ISO_A0, MediaSize.ISO_A0,
504                                 MediaSize.ISO_A1};
505                         Resolution[] resolutions = {RESOLUTION_300, RESOLUTION_300,
506                                 RESOLUTION_600};
507                         int[] colorModes = {PrintAttributes.COLOR_MODE_MONOCHROME,
508                                 PrintAttributes.COLOR_MODE_COLOR};
509                         int[] duplexModes = {PrintAttributes.DUPLEX_MODE_NONE,
510                                 PrintAttributes.DUPLEX_MODE_LONG_EDGE,
511                                 PrintAttributes.DUPLEX_MODE_SHORT_EDGE};
512 
513                         ArrayList<PrinterInfo> printers = new ArrayList<>();
514                         for (int mediaSizeIndex = 1; mediaSizeIndex < mediaSizes.length;
515                              mediaSizeIndex++) {
516                             for (int resolutionIndex = 1; resolutionIndex < mediaSizes.length;
517                                  resolutionIndex++) {
518                                 for (int colorIndex = 1; colorIndex < colorModes.length;
519                                      colorIndex++) {
520                                     for (int duplexIndex = 1; duplexIndex < duplexModes.length;
521                                          duplexIndex++) {
522                                         PrinterId printerId = session.getService()
523                                                 .generatePrinterId(Integer.valueOf(printers.size())
524                                                         .toString());
525 
526                                         PrinterCapabilitiesInfo.Builder b =
527                                                 new PrinterCapabilitiesInfo.Builder(printerId);
528 
529                                         for (int i = 0; i < mediaSizeIndex; i++) {
530                                             b.addMediaSize(mediaSizes[i], i == mediaSizeIndex - 1);
531                                         }
532 
533                                         for (int i = 0; i < resolutionIndex; i++) {
534                                             b.addResolution(resolutions[i],
535                                                     i == resolutionIndex - 1);
536                                         }
537 
538                                         int allColors = 0;
539                                         for (int i = 0; i < colorIndex; i++) {
540                                             allColors |= colorModes[i];
541                                         }
542                                         b.setColorModes(allColors, colorModes[colorIndex - 1]);
543 
544                                         int allDuplexModes = 0;
545                                         for (int i = 0; i < duplexIndex; i++) {
546                                             allDuplexModes |= duplexModes[i];
547                                         }
548                                         b.setDuplexModes(allDuplexModes,
549                                                 duplexModes[duplexIndex - 1]);
550 
551                                         printers.add((new PrinterInfo.Builder(printerId,
552                                                 Integer.valueOf(printers.size()).toString(),
553                                                 PrinterInfo.STATUS_IDLE)).setCapabilities(b.build())
554                                                 .build());
555                                     }
556                                 }
557                             }
558                         }
559 
560                         session.addPrinters(printers);
561 
562                         onPrinterDiscoverySessionCreateCalled();
563                         return null;
564                     }
565                 }, null, null, null, null, null, new Answer<Void>() {
566                     @Override
567                     public Void answer(InvocationOnMock invocation) {
568                         onPrinterDiscoverySessionDestroyCalled();
569                         return null;
570                     }
571                 });
572 
573         // Create the service callbacks for the first print service.
574         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
575                 new Answer<PrinterDiscoverySessionCallbacks>() {
576                     @Override
577                     public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
578                         return firstSessionCallbacks;
579                     }
580                 }, null, null);
581 
582         // Configure the print services.
583         FirstPrintService.setCallbacks(firstServiceCallbacks);
584         SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
585 
586         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(null, null, null);
587 
588         // Start printing.
589         print(adapter);
590 
591         waitForPrinterDiscoverySessionCreateCallbackCalled();
592 
593         getUiDevice().pressBack();
594 
595         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
596     }
597 
598     /**
599      * Base test that performs a print operation with a give PrinterCapabilityInfo and run a test
600      * function before finishing.
601      *
602      * @throws Exception
603      */
testPrinterCapabilityInfo(final Function<PrinterId, PrinterCapabilitiesInfo> capBuilder, Consumer<PrintAttributes> test)604     private void testPrinterCapabilityInfo(final Function<PrinterId, PrinterCapabilitiesInfo>
605             capBuilder, Consumer<PrintAttributes> test) throws Exception {
606         if (!supportsPrinting()) {
607             return;
608         }
609 
610         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
611                 createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
612                     @Override
613                     public Void answer(InvocationOnMock invocation) {
614                         StubbablePrinterDiscoverySession session =
615                                 ((PrinterDiscoverySessionCallbacks)
616                                         invocation.getMock()).getSession();
617 
618                         PrinterId printerId = session.getService()
619                                 .generatePrinterId(PRINTER_NAME);
620 
621                         ArrayList<PrinterInfo> printers = new ArrayList<>();
622                         printers.add((new PrinterInfo.Builder(printerId, PRINTER_NAME,
623                                 PrinterInfo.STATUS_IDLE))
624                                 .setCapabilities(capBuilder.apply(printerId)).build());
625 
626                         session.addPrinters(printers);
627 
628                         onPrinterDiscoverySessionCreateCalled();
629                         return null;
630                     }
631                 }, null, null, null, null, null, new Answer<Void>() {
632                     @Override
633                     public Void answer(InvocationOnMock invocation) {
634                         onPrinterDiscoverySessionDestroyCalled();
635                         return null;
636                     }
637                 });
638 
639         // Create the service callbacks for the first print service.
640         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
641                 new Answer<PrinterDiscoverySessionCallbacks>() {
642                     @Override
643                     public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
644                         return firstSessionCallbacks;
645                     }
646                 }, null, null);
647 
648         // Configure the print services.
649         FirstPrintService.setCallbacks(firstServiceCallbacks);
650         SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
651 
652         final PrintAttributes[] layoutAttributes = new PrintAttributes[1];
653 
654         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
655                 new Answer<Void>() {
656                     @Override
657                     public Void answer(InvocationOnMock invocation) throws Throwable {
658                         LayoutResultCallback callback = (LayoutResultCallback) invocation
659                                 .getArguments()[3];
660                         PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
661                                 .setPageCount(1)
662                                 .build();
663                         layoutAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
664 
665                         callback.onLayoutFinished(info, true);
666                         return null;
667                     }
668                 },
669                 new Answer<Void>() {
670                     @Override
671                     public Void answer(InvocationOnMock invocation) throws Throwable {
672                         Object[] args = invocation.getArguments();
673                         PageRange[] pages = (PageRange[]) args[0];
674                         ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
675                         WriteResultCallback callback = (WriteResultCallback) args[3];
676 
677                         writeBlankPages(layoutAttributes[0], fd, pages[0].getStart(),
678                                 pages[0].getEnd());
679                         fd.close();
680 
681                         callback.onWriteFinished(pages);
682                         return null;
683                     }
684                 }, null);
685 
686         // Start printing.
687         print(adapter);
688 
689         // make sure that options does not crash
690         openPrintOptions();
691 
692         // Select printer under test
693         selectPrinter(PRINTER_NAME);
694 
695         clickPrintButton();
696 
697         answerPrintServicesWarning(true);
698 
699         test.accept(layoutAttributes[0]);
700 
701         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
702     }
703 
704     /**
705      * That that you use a default color that is not in the allowed colors. This is allowed because
706      * of historical reasons.
707      *
708      * @throws Exception If anything is unexpected
709      */
testInvalidDefaultColor()710     public void testInvalidDefaultColor() throws Exception {
711         testPrinterCapabilityInfo(
712                 (printerId) -> (new PrinterCapabilitiesInfo.Builder(printerId))
713                         .addMediaSize(MediaSize.ISO_A4, true)
714                         .addResolution(RESOLUTION_300, true)
715                         .setColorModes(PrintAttributes.COLOR_MODE_MONOCHROME,
716                                 PrintAttributes.COLOR_MODE_COLOR).build(),
717                 (layoutAttributes) -> assertEquals(layoutAttributes.getColorMode(),
718                         PrintAttributes.COLOR_MODE_MONOCHROME));
719     }
720 
721     /**
722      * That that you use a default duplex mode that is not in the allowed duplex modes. This is
723      * allowed because of historical reasons.
724      *
725      * @throws Exception If anything is unexpected
726      */
testInvalidDefaultDuplexMode()727     public void testInvalidDefaultDuplexMode() throws Exception {
728         testPrinterCapabilityInfo(
729                 (printerId) -> (new PrinterCapabilitiesInfo.Builder(printerId))
730                         .addMediaSize(MediaSize.ISO_A4, true)
731                         .addResolution(RESOLUTION_300, true)
732                         .setColorModes(PrintAttributes.COLOR_MODE_MONOCHROME,
733                                 PrintAttributes.COLOR_MODE_MONOCHROME)
734                         .setDuplexModes(PrintAttributes.DUPLEX_MODE_LONG_EDGE
735                                 | PrintAttributes.DUPLEX_MODE_NONE,
736                                 PrintAttributes.DUPLEX_MODE_SHORT_EDGE).build(),
737                 (layoutAttributes) -> assertTrue(layoutAttributes.getDuplexMode() ==
738                         PrintAttributes.DUPLEX_MODE_LONG_EDGE || layoutAttributes.getDuplexMode() ==
739                         PrintAttributes.DUPLEX_MODE_NONE));
740     }
741 }
742