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