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 static android.print.test.Utils.eventually;
20 
21 import android.os.ParcelFileDescriptor;
22 import android.platform.test.annotations.AppModeFull;
23 import android.print.PageRange;
24 import android.print.PrintAttributes;
25 import android.print.PrintAttributes.Margins;
26 import android.print.PrintAttributes.MediaSize;
27 import android.print.PrintAttributes.Resolution;
28 import android.print.PrintDocumentAdapter;
29 import android.print.PrintDocumentAdapter.LayoutResultCallback;
30 import android.print.PrintDocumentAdapter.WriteResultCallback;
31 import android.print.PrintDocumentInfo;
32 import android.print.PrintJobInfo;
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.CustomPrintOptionsActivity;
38 import android.print.test.services.FirstPrintService;
39 import android.print.test.services.PrintServiceCallbacks;
40 import android.print.test.services.PrinterDiscoverySessionCallbacks;
41 import android.print.test.services.SecondPrintService;
42 import android.print.test.services.StubbablePrinterDiscoverySession;
43 import android.util.Log;
44 
45 import androidx.test.runner.AndroidJUnit4;
46 
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.concurrent.TimeoutException;
55 
56 /**
57  * This test verifies changes to the printer capabilities are applied correctly.
58  */
59 @AppModeFull(reason = "Print UI cannot resolve custom print options activity in a instant app")
60 @RunWith(AndroidJUnit4.class)
61 public class CustomPrintOptionsTest extends BasePrintTest {
62     private final static String LOG_TAG = "CustomPrintOptionsTest";
63     private static final String PRINTER_NAME = "Test printer";
64     private static final int MAX_TRIES = 10;
65 
66     // Default settings
67     private final PageRange[] DEFAULT_PAGES = new PageRange[] { new PageRange(0, 0) };
68     private final MediaSize DEFAULT_MEDIA_SIZE = MediaSize.ISO_A0;
69     private final int DEFAULT_COLOR_MODE = PrintAttributes.COLOR_MODE_COLOR;
70     private final int DEFAULT_DUPLEX_MODE = PrintAttributes.DUPLEX_MODE_LONG_EDGE;
71     private final Resolution DEFAULT_RESOLUTION = new Resolution("300x300", "300x300", 300, 300);
72     private final Margins DEFAULT_MARGINS = new Margins(0, 0, 0, 0);
73 
74     // All settings that are tested
75     private final PageRange[][] PAGESS = { DEFAULT_PAGES, new PageRange[] { new PageRange(1, 1) },
76             new PageRange[] { new PageRange(0, 2) }
77     };
78     private final MediaSize[] MEDIA_SIZES = { DEFAULT_MEDIA_SIZE, MediaSize.ISO_B0 };
79     private final Integer[] COLOR_MODES = { DEFAULT_COLOR_MODE,
80             PrintAttributes.COLOR_MODE_MONOCHROME
81     };
82     private final Integer[] DUPLEX_MODES = { DEFAULT_DUPLEX_MODE, PrintAttributes.DUPLEX_MODE_NONE
83     };
84     private final Resolution[] RESOLUTIONS = { DEFAULT_RESOLUTION,
85             new Resolution("600x600", "600x600", 600, 600)
86     };
87 
88     private PrintAttributes mLayoutAttributes;
89     private PrintDocumentAdapter mAdapter;
90     private static boolean sHasDefaultPrinterSet;
91 
92     /**
93      * Get the page ranges currently selected as described in the UI.
94      *
95      * @return Only page ranges from {@link #PAGESS} are detected correctly.
96      *
97      * @throws Exception If something was unexpected
98      */
getPages()99     private PageRange[] getPages() throws Exception {
100         String pageRange = mPrintHelper.getPageRange(3);
101         if (pageRange.equals("All 3")) {
102             return PAGESS[2];
103         }
104 
105         if (pageRange.equals("2")) {
106             return PAGESS[1];
107         }
108         if (pageRange.equals("1")) {
109             return PAGESS[0];
110         }
111         return null;
112     }
113 
114     @Before
setUpServicesAndAdapter()115     public void setUpServicesAndAdapter() {
116         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
117                 createMockPrinterDiscoverySessionCallbacks(invocation -> {
118                     StubbablePrinterDiscoverySession session =
119                             ((PrinterDiscoverySessionCallbacks) invocation.getMock())
120                                     .getSession();
121                     PrinterId printerId = session.getService().generatePrinterId(PRINTER_NAME);
122                     List<PrinterInfo> printers = new ArrayList<>(1);
123                     PrinterCapabilitiesInfo.Builder builder =
124                             new PrinterCapabilitiesInfo.Builder(printerId);
125 
126                     builder.setMinMargins(DEFAULT_MARGINS)
127                             .setColorModes(COLOR_MODES[0] | COLOR_MODES[1],
128                                     DEFAULT_COLOR_MODE)
129                             .setDuplexModes(DUPLEX_MODES[0] | DUPLEX_MODES[1],
130                                     DEFAULT_DUPLEX_MODE)
131                             .addMediaSize(DEFAULT_MEDIA_SIZE, true)
132                             .addMediaSize(MEDIA_SIZES[1], false)
133                             .addResolution(DEFAULT_RESOLUTION, true)
134                             .addResolution(RESOLUTIONS[1], false);
135 
136                     printers.add(new PrinterInfo.Builder(printerId, PRINTER_NAME,
137                             PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build());
138 
139                     session.addPrinters(printers);
140                     return null;
141                 }, null, null, null, null, null, invocation -> {
142                     onPrinterDiscoverySessionDestroyCalled();
143                     return null;
144                 });
145 
146         mAdapter = createMockPrintDocumentAdapter(
147                 invocation -> {
148                     LayoutResultCallback callback = (LayoutResultCallback) invocation
149                             .getArguments()[3];
150                     PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
151                             .setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
152                             .setPageCount(3)
153                             .build();
154 
155                     synchronized (CustomPrintOptionsTest.this) {
156                         mLayoutAttributes = (PrintAttributes) invocation.getArguments()[1];
157 
158                         CustomPrintOptionsTest.this.notifyAll();
159                     }
160 
161                     callback.onLayoutFinished(info, true);
162                     return null;
163                 },
164                 invocation -> {
165                     Object[] args = invocation.getArguments();
166                     ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
167                     WriteResultCallback callback = (WriteResultCallback) args[3];
168 
169                     PageRange[] writtenPages = (PageRange[]) args[0];
170 
171                     writeBlankPages(mLayoutAttributes, fd, writtenPages[0].getStart(),
172                             writtenPages[0].getEnd());
173                     fd.close();
174 
175                     callback.onWriteFinished(writtenPages);
176 
177                     onWriteCalled();
178 
179                     return null;
180                 }, null);
181 
182         // Create the service callbacks for the first print service.
183         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
184                 invocation -> firstSessionCallbacks, null, null);
185 
186         // Configure the print services.
187         FirstPrintService.setCallbacks(firstServiceCallbacks);
188         SecondPrintService.setCallbacks(createMockPrintServiceCallbacks(null, null, null));
189 
190         // Set default printer
191         if (!sHasDefaultPrinterSet) {
192             // This is the first print test that runs. If this is run after other tests these other
193             // test can still cause memory pressure and make the printactivity to go through a
194             // destroy-create cycle. In this case we have to retry to operation.
195             int tries = 0;
196             while (true) {
197                 try {
198                     resetCounters();
199                     makeDefaultPrinter(mAdapter, PRINTER_NAME);
200                     break;
201                 } catch (Throwable e) {
202                     if (getActivityDestroyCallbackCallCount() > 0 && tries < MAX_TRIES) {
203                         Log.e(LOG_TAG, "Activity was destroyed during test, retrying", e);
204 
205                         tries++;
206                         continue;
207                     }
208 
209                     throw new RuntimeException(e);
210                 }
211             }
212 
213             sHasDefaultPrinterSet = true;
214         }
215     }
216 
217     /**
218      * Test that we can switch to a specific set of settings via the custom print options activity
219      *
220      * @param copyFromOriginal If the print job info should be copied from the original
221      * @param numCopies        The copies to print
222      * @param pages            The page ranges to print
223      * @param mediaSize        The media size to use
224      * @param isPortrait       If the mediaSize is portrait
225      * @param colorMode        The color mode to use
226      * @param duplexMode       The duplex mode to use
227      * @param resolution       The resolution to use
228      *
229      * @throws Exception If anything is unexpected
230      */
testCase(final boolean copyFromOriginal, final Integer numCopies, final PageRange[] pages, final MediaSize mediaSize, final boolean isPortrait, final Integer colorMode, final Integer duplexMode, final Resolution resolution)231     private void testCase(final boolean copyFromOriginal, final Integer numCopies,
232             final PageRange[] pages, final MediaSize mediaSize, final boolean isPortrait,
233             final Integer colorMode, final Integer duplexMode, final Resolution resolution)
234             throws Throwable {
235         final PrintAttributes.Builder additionalAttributesBuilder = new PrintAttributes.Builder();
236         final PrintAttributes.Builder newAttributesBuilder = new PrintAttributes.Builder();
237 
238         newAttributesBuilder.setMinMargins(DEFAULT_MARGINS);
239 
240         if (mediaSize != null) {
241             if (isPortrait) {
242                 additionalAttributesBuilder.setMediaSize(mediaSize.asPortrait());
243                 newAttributesBuilder.setMediaSize(mediaSize.asPortrait());
244             } else {
245                 additionalAttributesBuilder.setMediaSize(mediaSize.asLandscape());
246                 newAttributesBuilder.setMediaSize(mediaSize.asLandscape());
247             }
248         } else {
249             newAttributesBuilder.setMediaSize(DEFAULT_MEDIA_SIZE);
250         }
251 
252         if (colorMode != null) {
253             additionalAttributesBuilder.setColorMode(colorMode);
254             newAttributesBuilder.setColorMode(colorMode);
255         } else {
256             newAttributesBuilder.setColorMode(DEFAULT_COLOR_MODE);
257         }
258 
259         if (duplexMode != null) {
260             additionalAttributesBuilder.setDuplexMode(duplexMode);
261             newAttributesBuilder.setDuplexMode(duplexMode);
262         } else {
263             newAttributesBuilder.setDuplexMode(DEFAULT_DUPLEX_MODE);
264         }
265 
266         if (resolution != null) {
267             additionalAttributesBuilder.setResolution(resolution);
268             newAttributesBuilder.setResolution(resolution);
269         } else {
270             newAttributesBuilder.setResolution(DEFAULT_RESOLUTION);
271         }
272 
273         CustomPrintOptionsActivity.setCallBack(
274                 (printJob, printer) -> {
275                     PrintJobInfo.Builder printJobBuilder;
276 
277                     if (copyFromOriginal) {
278                         printJobBuilder = new PrintJobInfo.Builder(printJob);
279                     } else {
280                         printJobBuilder = new PrintJobInfo.Builder(null);
281                     }
282 
283                     if (numCopies != null) {
284                         printJobBuilder.setCopies(numCopies);
285                     }
286 
287                     if (pages != null) {
288                         printJobBuilder.setPages(pages);
289                     }
290 
291                     if (mediaSize != null || colorMode != null || duplexMode != null
292                             || resolution != null) {
293                         printJobBuilder.setAttributes(additionalAttributesBuilder.build());
294                     }
295 
296                     return printJobBuilder.build();
297                 });
298 
299         // Check that the attributes were send to the print service
300         PrintAttributes newAttributes = newAttributesBuilder.build();
301         Log.i(LOG_TAG, "Change to attributes: " + newAttributes + ", copies: " + numCopies +
302                 ", pages: " + Arrays.toString(pages) + ", copyFromOriginal: " + copyFromOriginal);
303 
304         // This is the first print test that runs. If this is run after other tests these other test
305         // can still cause memory pressure and make the printactivity to go through a destroy-create
306         // cycle. In this case we have to retry to operation.
307         int tries = 0;
308         while (true) {
309             try {
310                 resetCounters();
311 
312                 // Start printing
313                 print(mAdapter);
314 
315                 // Wait for write.
316                 waitForWriteAdapterCallback(1);
317 
318                 // Open the print options.
319                 openPrintOptions();
320 
321                 // Apply options by executing callback above
322                 Log.d(LOG_TAG, "Apply changes");
323                 openCustomPrintOptions();
324 
325                 Log.d(LOG_TAG, "Check attributes");
326                 long endTime = System.currentTimeMillis() + OPERATION_TIMEOUT_MILLIS;
327                 synchronized (this) {
328                     while (mLayoutAttributes == null ||
329                             !mLayoutAttributes.equals(newAttributes)) {
330                         wait(Math.max(1, endTime - System.currentTimeMillis()));
331 
332                         if (endTime < System.currentTimeMillis()) {
333                             throw new TimeoutException(
334                                     "Print attributes did not change to " + newAttributes + " in " +
335                                             OPERATION_TIMEOUT_MILLIS + " ms. Current attributes"
336                                             + mLayoutAttributes);
337                         }
338                     }
339                 }
340 
341                 PageRange[] newPages;
342 
343                 if (pages == null) {
344                     newPages = new PageRange[] { new PageRange(0, 2) };
345                 } else {
346                     newPages = pages;
347                 }
348 
349                 Log.d(LOG_TAG, "Check pages");
350                 eventually(() -> {
351                     PageRange[] actualPages = getPages();
352                     if (!Arrays.equals(newPages, actualPages)) {
353                         throw new AssertionError(
354                                 "Expected " + Arrays.toString(newPages) + ", actual " +
355                                         Arrays.toString(actualPages));
356                     }
357                 });
358 
359                 break;
360             } catch (Throwable e) {
361                 if (getActivityDestroyCallbackCallCount() > 0 && tries < MAX_TRIES) {
362                     Log.e(LOG_TAG, "Activity was destroyed during test, retrying", e);
363 
364                     tries++;
365                     continue;
366                 }
367 
368                 throw e;
369             }
370         }
371 
372         // Abort printing
373         mPrintHelper.closeCustomPrintOptions();
374         mPrintHelper.closePrintOptions();
375         mPrintHelper.cancelPrinting();
376 
377         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
378     }
379 
380     @Test
changeToChangeEveryThingButPages()381     public void changeToChangeEveryThingButPages() throws Throwable {
382         testCase(false, 2, null, MEDIA_SIZES[1], false, COLOR_MODES[1], DUPLEX_MODES[1],
383                 RESOLUTIONS[1]);
384     }
385 
386     @Test
changeToAttributes()387     public void changeToAttributes() throws Throwable {
388         testCase(false, null, null, MEDIA_SIZES[1], false, COLOR_MODES[1], DUPLEX_MODES[1],
389                 RESOLUTIONS[1]);
390     }
391 
392     @Test
changeToNonAttributes()393     public void changeToNonAttributes() throws Throwable {
394         testCase(false, 2, PAGESS[1], null, true, null, null, null);
395     }
396 
397     @Test
changeToAttributesNoCopy()398     public void changeToAttributesNoCopy() throws Throwable {
399         testCase(true, null, null, MEDIA_SIZES[1], false, COLOR_MODES[1], DUPLEX_MODES[1],
400                 RESOLUTIONS[1]);
401     }
402 
403     @Test
changeToNonAttributesNoCopy()404     public void changeToNonAttributesNoCopy() throws Throwable {
405         testCase(true, 2, PAGESS[1], null, true, null, null, null);
406     }
407 
408     @Test
changeToDefault()409     public void changeToDefault() throws Throwable {
410         testCase(false, 1, DEFAULT_PAGES, DEFAULT_MEDIA_SIZE, DEFAULT_MEDIA_SIZE.isPortrait(),
411                 DEFAULT_COLOR_MODE, DEFAULT_DUPLEX_MODE, DEFAULT_RESOLUTION);
412     }
413 
414     @Test
changeToDefaultNoCopy()415     public void changeToDefaultNoCopy() throws Throwable {
416         testCase(true, 1, DEFAULT_PAGES, DEFAULT_MEDIA_SIZE, DEFAULT_MEDIA_SIZE.isPortrait(),
417                 DEFAULT_COLOR_MODE, DEFAULT_DUPLEX_MODE, DEFAULT_RESOLUTION);
418     }
419 
420     @Test
changeToNothing()421     public void changeToNothing() throws Throwable {
422         testCase(false, null, null, null, true, null, null, null);
423     }
424 
425     @Test
testChangeToNothingNoCopy()426     public void testChangeToNothingNoCopy() throws Throwable {
427         testCase(true, null, null, null, true, null, null, null);
428     }
429 
430     @Test
changeToAllPages()431     public void changeToAllPages() throws Throwable {
432         testCase(false, null, PAGESS[2], null, true, null, null, null);
433     }
434 
435     @Test
changeToSomePages()436     public void changeToSomePages() throws Throwable {
437         testCase(false, null, PAGESS[1], null, true, null, null, null);
438     }
439 }
440