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