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