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 org.mockito.Mockito.inOrder;
20 
21 import android.os.ParcelFileDescriptor;
22 import android.print.PageRange;
23 import android.print.PrintAttributes;
24 import android.print.PrintAttributes.Margins;
25 import android.print.PrintAttributes.MediaSize;
26 import android.print.PrintAttributes.Resolution;
27 import android.print.PrintDocumentAdapter;
28 import android.print.PrintDocumentAdapter.LayoutResultCallback;
29 import android.print.PrintDocumentAdapter.WriteResultCallback;
30 import android.print.PrintDocumentInfo;
31 import android.print.PrinterCapabilitiesInfo;
32 import android.print.PrinterId;
33 import android.print.PrinterInfo;
34 import android.print.cts.services.FirstPrintService;
35 import android.print.cts.services.PrintServiceCallbacks;
36 import android.print.cts.services.PrinterDiscoverySessionCallbacks;
37 import android.print.cts.services.SecondPrintService;
38 import android.print.cts.services.StubbablePrinterDiscoverySession;
39 import android.printservice.PrintJob;
40 import android.printservice.PrinterDiscoverySession;
41 
42 import org.mockito.InOrder;
43 import org.mockito.exceptions.verification.VerificationInOrderFailure;
44 import org.mockito.invocation.InvocationOnMock;
45 import org.mockito.stubbing.Answer;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.List;
50 
51 /**
52  * This test verifies that the system respects the {@link PrinterDiscoverySession}
53  * contract is respected.
54  */
55 public class PrinterDiscoverySessionLifecycleTest extends BasePrintTest {
56     private static final String FIRST_PRINTER_NAME = "First printer";
57     private static final String SECOND_PRINTER_NAME = "Second printer";
58 
59     private static final String FIRST_PRINTER_LOCAL_ID= "first_printer";
60     private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
61 
testNormalLifecycle()62     public void testNormalLifecycle() throws Exception {
63         if (!supportsPrinting()) {
64             return;
65         }
66         // Create the session callbacks that we will be checking.
67         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
68                 createFirstMockPrinterDiscoverySessionCallbacks();
69 
70         // Create the service callbacks for the first print service.
71         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
72                 new Answer<PrinterDiscoverySessionCallbacks>() {
73                 @Override
74                 public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
75                         return firstSessionCallbacks;
76                     }
77                 },
78                 new Answer<Void>() {
79                 @Override
80                 public Void answer(InvocationOnMock invocation) {
81                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
82                     // We pretend the job is handled immediately.
83                     printJob.complete();
84                     return null;
85                 }
86             }, null);
87 
88         // Configure the print services.
89         FirstPrintService.setCallbacks(firstServiceCallbacks);
90         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
91 
92         // Create a print adapter that respects the print contract.
93         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter();
94 
95         // Start printing.
96         print(adapter);
97 
98         // Wait for write of the first page.
99         waitForWriteAdapterCallback(1);
100 
101         // Select the first printer.
102         selectPrinter(FIRST_PRINTER_NAME);
103 
104         // Wait for layout as the printer has different capabilities.
105         waitForLayoutAdapterCallbackCount(2);
106 
107         // Select the second printer (same capabilities as the other
108         // one so no layout should happen).
109         selectPrinter(SECOND_PRINTER_NAME);
110 
111         // While the printer discovery session is still alive store the
112         // ids of printers as we want to make some assertions about them
113         // but only the print service can create printer ids which means
114         // that we need to get the created ones.
115         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(firstSessionCallbacks,
116                 FIRST_PRINTER_LOCAL_ID);
117         PrinterId secondPrinterId = getAddedPrinterIdForLocalId(firstSessionCallbacks,
118                 SECOND_PRINTER_LOCAL_ID);
119         assertNotNull("Coundn't find printer:" + FIRST_PRINTER_LOCAL_ID, firstPrinterId);
120         assertNotNull("Coundn't find printer:" + SECOND_PRINTER_LOCAL_ID, secondPrinterId);
121 
122         // Click the print button.
123         clickPrintButton();
124 
125         // Answer the dialog for the print service cloud warning
126         answerPrintServicesWarning(true);
127 
128         // Wait for all print jobs to be handled after which the session destroyed.
129         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
130 
131         // Verify the expected calls.
132         InOrder inOrder = inOrder(firstSessionCallbacks);
133 
134         // We start discovery as the print dialog was up.
135         List<PrinterId> emptyPrinterIdList = Collections.emptyList();
136         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
137                 emptyPrinterIdList);
138 
139         // We selected the first printer and now it should be tracked.
140         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
141                 firstPrinterId);
142 
143         // We selected the second printer so the first should not be tracked.
144         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
145                 firstPrinterId);
146 
147         // We selected the second printer and now it should be tracked.
148         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
149                 secondPrinterId);
150 
151         // The print dialog went away so we first stop the printer tracking...
152         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
153                 secondPrinterId);
154 
155         // ... next we stop printer discovery...
156         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
157 
158         // ... last the session is destroyed.
159         inOrder.verify(firstSessionCallbacks).onDestroy();
160     }
161 
testCancelPrintServicesAlertDialog()162     public void testCancelPrintServicesAlertDialog() throws Exception {
163         if (!supportsPrinting()) {
164             return;
165         }
166         // Create the session callbacks that we will be checking.
167         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
168                 createFirstMockPrinterDiscoverySessionCallbacks();
169 
170         // Create the service callbacks for the first print service.
171         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
172                 new Answer<PrinterDiscoverySessionCallbacks>() {
173                     @Override
174                     public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
175                         return firstSessionCallbacks;
176                     }
177                 },
178                 new Answer<Void>() {
179                     @Override
180                     public Void answer(InvocationOnMock invocation) {
181                         PrintJob printJob = (PrintJob) invocation.getArguments()[0];
182                         // We pretend the job is handled immediately.
183                         printJob.complete();
184                         return null;
185                     }
186                 }, null);
187 
188         // Configure the print services.
189         FirstPrintService.setCallbacks(firstServiceCallbacks);
190         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
191 
192         // Create a print adapter that respects the print contract.
193         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter();
194 
195         // Start printing.
196         print(adapter);
197 
198         // Wait for write of the first page.
199         waitForWriteAdapterCallback(1);
200 
201         // Select the first printer.
202         selectPrinter(FIRST_PRINTER_NAME);
203 
204         // While the printer discovery session is still alive store the
205         // ids of printers as we want to make some assertions about them
206         // but only the print service can create printer ids which means
207         // that we need to get the created ones.
208         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(firstSessionCallbacks,
209                 FIRST_PRINTER_LOCAL_ID);
210         assertNotNull("Coundn't find printer:" + FIRST_PRINTER_LOCAL_ID, firstPrinterId);
211 
212         // Click the print button.
213         clickPrintButton();
214 
215         // Cancel the dialog for the print service cloud warning
216         answerPrintServicesWarning(false);
217 
218         // Click the print button again.
219         clickPrintButton();
220 
221         // Answer the dialog for the print service cloud warning
222         answerPrintServicesWarning(true);
223 
224         // Wait for all print jobs to be handled after which the session destroyed.
225         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
226 
227         // Verify the expected calls.
228         InOrder inOrder = inOrder(firstSessionCallbacks);
229 
230         // We start discovery as the print dialog was up.
231         List<PrinterId> emptyPrinterIdList = Collections.emptyList();
232         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
233                 emptyPrinterIdList);
234 
235         // We selected the first printer and now it should be tracked.
236         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
237                 firstPrinterId);
238 
239         // We selected the second printer so the first should not be tracked.
240         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
241                 firstPrinterId);
242 
243         // ... next we stop printer discovery...
244         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
245 
246         // ... last the session is destroyed.
247         inOrder.verify(firstSessionCallbacks).onDestroy();
248     }
249 
testStartPrinterDiscoveryWithHistoricalPrinters()250     public void testStartPrinterDiscoveryWithHistoricalPrinters() throws Exception {
251         if (!supportsPrinting()) {
252             return;
253         }
254         // Create the session callbacks that we will be checking.
255         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
256                 createFirstMockPrinterDiscoverySessionCallbacks();
257 
258         // Create the service callbacks for the first print service.
259         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
260                 new Answer<PrinterDiscoverySessionCallbacks>() {
261                 @Override
262                 public PrinterDiscoverySessionCallbacks answer(InvocationOnMock invocation) {
263                         return firstSessionCallbacks;
264                     }
265                 },
266                 new Answer<Void>() {
267                 @Override
268                 public Void answer(InvocationOnMock invocation) {
269                     PrintJob printJob = (PrintJob) invocation.getArguments()[0];
270                     // We pretend the job is handled immediately.
271                     printJob.complete();
272                     return null;
273                 }
274             }, null);
275 
276         // Configure the print services.
277         FirstPrintService.setCallbacks(firstServiceCallbacks);
278         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
279 
280         // Create a print adapter that respects the print contract.
281         PrintDocumentAdapter adapter = createMockPrintDocumentAdapter();
282 
283         // Start printing.
284         print(adapter);
285 
286         // Wait for write of the first page.
287         waitForWriteAdapterCallback(1);
288 
289         // Select the first printer.
290         selectPrinter(FIRST_PRINTER_NAME);
291 
292         // Wait for a layout to finish - first layout was for the
293         // PDF printer, second for the first printer in preview mode.
294         waitForLayoutAdapterCallbackCount(2);
295 
296         // While the printer discovery session is still alive store the
297         // ids of printer as we want to make some assertions about it
298         // but only the print service can create printer ids which means
299         // that we need to get the created one.
300         PrinterId firstPrinterId = getAddedPrinterIdForLocalId(
301                 firstSessionCallbacks, FIRST_PRINTER_LOCAL_ID);
302 
303         // Click the print button.
304         clickPrintButton();
305 
306         // Answer the dialog for the print service cloud warning
307         answerPrintServicesWarning(true);
308 
309         // Wait for the print to complete.
310         waitForAdapterFinishCallbackCalled();
311 
312         // Now print again as we want to confirm that the start
313         // printer discovery passes in the priority list.
314         print(adapter);
315 
316         // Wait for a layout to finish - first layout was for the
317         // PDF printer, second for the first printer in preview mode,
318         // the third for the first printer in non-preview mode, and
319         // now a fourth for the PDF printer as we are printing again.
320         waitForLayoutAdapterCallbackCount(4);
321 
322         // Cancel the printing.
323         getUiDevice().pressBack(); // wakes up the device.
324         getUiDevice().pressBack();
325 
326         // Wait for all print jobs to be handled after which the is session destroyed.
327         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
328 
329         // Verify the expected calls.
330         InOrder inOrder = inOrder(firstSessionCallbacks);
331 
332         // We start discovery with no printer history.
333         List<PrinterId> priorityList = new ArrayList<PrinterId>();
334         inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(
335                 priorityList);
336 
337         // We selected the first printer and now it should be tracked.
338         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
339                 firstPrinterId);
340 
341         // We confirmed print so the first should not be tracked.
342         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
343                 firstPrinterId);
344 
345         // This is tricky. It is possible that the print activity was not
346         // destroyed (the platform delays destruction at convenient time as
347         // an optimization) and we get the same instance which means that
348         // the discovery session may not have been destroyed. We try the
349         // case with the activity being destroyed and if this fails the
350         // case with the activity brought to front.
351         priorityList.add(firstPrinterId);
352         try {
353             inOrder.verify(firstSessionCallbacks).onStartPrinterDiscovery(priorityList);
354         } catch (VerificationInOrderFailure error) {
355             inOrder.verify(firstSessionCallbacks).onValidatePrinters(priorityList);
356         }
357 
358         // The system selects the highest ranked historical printer.
359         inOrder.verify(firstSessionCallbacks).onStartPrinterStateTracking(
360                 firstPrinterId);
361 
362         // We canceled print so the first should not be tracked.
363         inOrder.verify(firstSessionCallbacks).onStopPrinterStateTracking(
364                 firstPrinterId);
365 
366 
367         // Discovery is always stopped before the session is always destroyed.
368         inOrder.verify(firstSessionCallbacks).onStopPrinterDiscovery();
369 
370         // ...last the session is destroyed.
371         inOrder.verify(firstSessionCallbacks).onDestroy();
372     }
373 
getAddedPrinterIdForLocalId( final PrinterDiscoverySessionCallbacks sessionCallbacks, String printerLocalId)374     private PrinterId getAddedPrinterIdForLocalId(
375             final PrinterDiscoverySessionCallbacks sessionCallbacks, String printerLocalId) {
376         final List<PrinterInfo> reportedPrinters = new ArrayList<PrinterInfo>();
377         getInstrumentation().runOnMainSync(new Runnable() {
378             @Override
379             public void run() {
380                 // Grab the printer ids as only the service can create such.
381                 StubbablePrinterDiscoverySession session = sessionCallbacks.getSession();
382                 reportedPrinters.addAll(session.getPrinters());
383             }
384         });
385 
386         final int reportedPrinterCount = reportedPrinters.size();
387         for (int i = 0; i < reportedPrinterCount; i++) {
388             PrinterInfo reportedPrinter = reportedPrinters.get(i);
389             String localId = reportedPrinter.getId().getLocalId();
390             if (printerLocalId.equals(localId)) {
391                 return reportedPrinter.getId();
392             }
393         }
394 
395         return null;
396     }
397 
createSecondMockPrintServiceCallbacks()398     private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
399         return createMockPrintServiceCallbacks(null, null, null);
400     }
401 
createFirstMockPrinterDiscoverySessionCallbacks()402     private PrinterDiscoverySessionCallbacks createFirstMockPrinterDiscoverySessionCallbacks() {
403         return createMockPrinterDiscoverySessionCallbacks(new Answer<Void>() {
404             @Override
405             public Void answer(InvocationOnMock invocation) {
406                 // Get the session.
407                 StubbablePrinterDiscoverySession session = ((PrinterDiscoverySessionCallbacks)
408                         invocation.getMock()).getSession();
409 
410                 if (session.getPrinters().isEmpty()) {
411                     List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
412 
413                     // Add the first printer.
414                     PrinterId firstPrinterId = session.getService().generatePrinterId(
415                             FIRST_PRINTER_LOCAL_ID);
416                     PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
417                             FIRST_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
418                         .build();
419                     printers.add(firstPrinter);
420 
421                     // Add the first printer.
422                     PrinterId secondPrinterId = session.getService().generatePrinterId(
423                             SECOND_PRINTER_LOCAL_ID);
424                     PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
425                             SECOND_PRINTER_NAME, PrinterInfo.STATUS_IDLE)
426                         .build();
427                     printers.add(secondPrinter);
428 
429                     session.addPrinters(printers);
430                 }
431                 return null;
432             }
433         }, null, null, new Answer<Void>() {
434             @Override
435             public Void answer(InvocationOnMock invocation) throws Throwable {
436                 // Get the session.
437                 StubbablePrinterDiscoverySession session = ((PrinterDiscoverySessionCallbacks)
438                         invocation.getMock()).getSession();
439 
440                 PrinterId trackedPrinterId = (PrinterId) invocation.getArguments()[0];
441                 List<PrinterInfo> reportedPrinters = session.getPrinters();
442 
443                 // We should be tracking a printer that we added.
444                 PrinterInfo trackedPrinter = null;
445                 final int reportedPrinterCount = reportedPrinters.size();
446                 for (int i = 0; i < reportedPrinterCount; i++) {
447                     PrinterInfo reportedPrinter = reportedPrinters.get(i);
448                     if (reportedPrinter.getId().equals(trackedPrinterId)) {
449                         trackedPrinter = reportedPrinter;
450                         break;
451                     }
452                 }
453                 assertNotNull("Can track only added printers", trackedPrinter);
454 
455                 // If the printer does not have capabilities reported add them.
456                 if (trackedPrinter.getCapabilities() == null) {
457 
458                     // Add the capabilities to emulate lazy discovery.
459                     // Same for each printer is fine for what we test.
460                     PrinterCapabilitiesInfo capabilities =
461                             new PrinterCapabilitiesInfo.Builder(trackedPrinterId)
462                         .setMinMargins(new Margins(200, 200, 200, 200))
463                         .addMediaSize(MediaSize.ISO_A4, true)
464                         .addMediaSize(MediaSize.ISO_A5, false)
465                         .addResolution(new Resolution("300x300", "300x300", 300, 300), true)
466                         .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
467                                 PrintAttributes.COLOR_MODE_COLOR)
468                         .build();
469                     PrinterInfo updatedPrinter = new PrinterInfo.Builder(trackedPrinter)
470                         .setCapabilities(capabilities)
471                         .build();
472 
473                     // Update the printer.
474                     List<PrinterInfo> printers = new ArrayList<PrinterInfo>();
475                     printers.add(updatedPrinter);
476                     session.addPrinters(printers);
477                 }
478 
479                 return null;
480             }
481         }, null, null, new Answer<Void>() {
482             @Override
483             public Void answer(InvocationOnMock invocation) throws Throwable {
484                 // Take a note onDestroy was called.
485                 onPrinterDiscoverySessionDestroyCalled();
486                 return null;
487             }
488         });
489     }
490 
491     public PrintDocumentAdapter createMockPrintDocumentAdapter() {
492         final PrintAttributes[] printAttributes = new PrintAttributes[1];
493 
494         return createMockPrintDocumentAdapter(
495             new Answer<Void>() {
496             @Override
497             public Void answer(InvocationOnMock invocation) throws Throwable {
498                 printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
499                 LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
500                 PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
501                         .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
502                         .setPageCount(3)
503                         .build();
504                 callback.onLayoutFinished(info, false);
505                 // Mark layout was called.
506                 onLayoutCalled();
507                 return null;
508             }
509         }, new Answer<Void>() {
510             @Override
511             public Void answer(InvocationOnMock invocation) throws Throwable {
512                 Object[] args = invocation.getArguments();
513                 PageRange[] pages = (PageRange[]) args[0];
514                 ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
515                 WriteResultCallback callback = (WriteResultCallback) args[3];
516                 writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
517                 fd.close();
518                 callback.onWriteFinished(pages);
519                 // Mark write was called.
520                 onWriteCalled();
521                 return null;
522             }
523         }, new Answer<Void>() {
524             @Override
525             public Void answer(InvocationOnMock invocation) throws Throwable {
526                 // Mark finish was called.
527                 onFinishCalled();
528                 return null;
529             }
530         });
531     }
532 }
533