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 17package android.print; 18 19import static org.junit.Assert.assertTrue; 20import static org.junit.Assert.fail; 21import static org.junit.Assume.assumeTrue; 22import static org.mockito.Matchers.any; 23import static org.mockito.Mockito.doAnswer; 24import static org.mockito.Mockito.doCallRealMethod; 25import static org.mockito.Mockito.mock; 26import static org.mockito.Mockito.when; 27 28import android.annotation.NonNull; 29import android.app.Instrumentation; 30import android.content.Context; 31import android.content.pm.PackageManager; 32import android.os.CancellationSignal; 33import android.os.ParcelFileDescriptor; 34import android.os.SystemClock; 35import android.print.mockservice.PrintServiceCallbacks; 36import android.print.mockservice.PrinterDiscoverySessionCallbacks; 37import android.print.mockservice.StubbablePrinterDiscoverySession; 38import android.printservice.CustomPrinterIconCallback; 39import android.printservice.PrintJob; 40import android.printservice.PrintService; 41import android.support.test.InstrumentationRegistry; 42import android.support.test.uiautomator.UiDevice; 43import android.support.test.rule.ActivityTestRule; 44 45import org.junit.After; 46import org.junit.Before; 47import org.junit.BeforeClass; 48import org.junit.Rule; 49import org.mockito.stubbing.Answer; 50 51import java.io.FileInputStream; 52import java.io.IOException; 53import java.util.List; 54import java.util.concurrent.TimeoutException; 55 56/** 57 * This is the base class for print tests. 58 */ 59abstract class BasePrintTest { 60 protected static final long OPERATION_TIMEOUT = 30000; 61 private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success"; 62 private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT 63 64 private android.print.PrintJob mPrintJob; 65 66 private CallCounter mStartCallCounter; 67 private CallCounter mStartSessionCallCounter; 68 69 private static Instrumentation sInstrumentation; 70 private static UiDevice sUiDevice; 71 72 @Rule 73 public ActivityTestRule<PrintTestActivity> mActivityRule = 74 new ActivityTestRule<>(PrintTestActivity.class, false, true); 75 76 /** 77 * {@link Runnable} that can throw and {@link Exception} 78 */ 79 interface Invokable { 80 /** 81 * Execute the invokable 82 * 83 * @throws Exception 84 */ 85 void run() throws Exception; 86 } 87 88 /** 89 * Assert that the invokable throws an expectedException 90 * 91 * @param invokable The {@link Invokable} to run 92 * @param expectedClass The {@link Exception} that is supposed to be thrown 93 */ 94 void assertException(Invokable invokable, Class<? extends Exception> expectedClass) 95 throws Exception { 96 try { 97 invokable.run(); 98 } catch (Exception e) { 99 if (e.getClass().isAssignableFrom(expectedClass)) { 100 return; 101 } else { 102 throw e; 103 } 104 } 105 106 throw new AssertionError("No exception thrown"); 107 } 108 109 /** 110 * Return the UI device 111 * 112 * @return the UI device 113 */ 114 public UiDevice getUiDevice() { 115 return sUiDevice; 116 } 117 118 protected static Instrumentation getInstrumentation() { 119 return sInstrumentation; 120 } 121 122 @BeforeClass 123 public static void setUpClass() throws Exception { 124 sInstrumentation = InstrumentationRegistry.getInstrumentation(); 125 assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature( 126 PackageManager.FEATURE_PRINTING)); 127 128 sUiDevice = UiDevice.getInstance(sInstrumentation); 129 130 // Make sure we start with a clean slate. 131 clearPrintSpoolerData(); 132 133 // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2 134 // Dexmaker is used by mockito. 135 System.setProperty("dexmaker.dexcache", getInstrumentation() 136 .getTargetContext().getCacheDir().getPath()); 137 } 138 139 @Before 140 public void initCounters() throws Exception { 141 // Initialize the latches. 142 mStartCallCounter = new CallCounter(); 143 mStartSessionCallCounter = new CallCounter(); 144 } 145 146 @After 147 public void exitActivities() throws Exception { 148 // Exit print spooler 149 getUiDevice().pressBack(); 150 getUiDevice().pressBack(); 151 } 152 153 protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter, 154 final PrintAttributes attributes) { 155 // Initiate printing as if coming from the app. 156 getInstrumentation().runOnMainSync(() -> { 157 PrintManager printManager = (PrintManager) getActivity() 158 .getSystemService(Context.PRINT_SERVICE); 159 mPrintJob = printManager.print("Print job", adapter, attributes); 160 }); 161 162 return mPrintJob; 163 } 164 165 protected void onStartCalled() { 166 mStartCallCounter.call(); 167 } 168 169 protected void onPrinterDiscoverySessionStartCalled() { 170 mStartSessionCallCounter.call(); 171 } 172 173 protected void waitForPrinterDiscoverySessionStartCallbackCalled() { 174 waitForCallbackCallCount(mStartSessionCallCounter, 1, 175 "Did not get expected call to onStartPrinterDiscoverySession."); 176 } 177 178 protected void waitForStartAdapterCallbackCalled() { 179 waitForCallbackCallCount(mStartCallCounter, 1, "Did not get expected call to start."); 180 } 181 182 private static void waitForCallbackCallCount(CallCounter counter, int count, String message) { 183 try { 184 counter.waitForCount(count, OPERATION_TIMEOUT); 185 } catch (TimeoutException te) { 186 fail(message); 187 } 188 } 189 190 protected PrintTestActivity getActivity() { 191 return mActivityRule.getActivity(); 192 } 193 194 public static String runShellCommand(Instrumentation instrumentation, String cmd) 195 throws IOException { 196 ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd); 197 byte[] buf = new byte[512]; 198 int bytesRead; 199 FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd); 200 StringBuilder stdout = new StringBuilder(); 201 while ((bytesRead = fis.read(buf)) != -1) { 202 stdout.append(new String(buf, 0, bytesRead)); 203 } 204 fis.close(); 205 return stdout.toString(); 206 } 207 208 protected static void clearPrintSpoolerData() throws Exception { 209 assertTrue("failed to clear print spooler data", 210 runShellCommand(getInstrumentation(), String.format( 211 "pm clear --user %d %s", CURRENT_USER_ID, 212 PrintManager.PRINT_SPOOLER_PACKAGE_NAME)) 213 .contains(PM_CLEAR_SUCCESS_OUTPUT)); 214 } 215 216 @SuppressWarnings("unchecked") 217 protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks( 218 Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, 219 Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, 220 Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking, 221 Answer<Void> onDestroy) { 222 PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class); 223 224 doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class)); 225 when(callbacks.getSession()).thenCallRealMethod(); 226 227 if (onStartPrinterDiscovery != null) { 228 doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery( 229 any(List.class)); 230 } 231 if (onStopPrinterDiscovery != null) { 232 doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery(); 233 } 234 if (onValidatePrinters != null) { 235 doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters( 236 any(List.class)); 237 } 238 if (onStartPrinterStateTracking != null) { 239 doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking( 240 any(PrinterId.class)); 241 } 242 if (onRequestCustomPrinterIcon != null) { 243 doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon( 244 any(PrinterId.class), any(CancellationSignal.class), 245 any(CustomPrinterIconCallback.class)); 246 } 247 if (onStopPrinterStateTracking != null) { 248 doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking( 249 any(PrinterId.class)); 250 } 251 if (onDestroy != null) { 252 doAnswer(onDestroy).when(callbacks).onDestroy(); 253 } 254 255 return callbacks; 256 } 257 258 protected PrintServiceCallbacks createMockPrintServiceCallbacks( 259 Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, 260 Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) { 261 final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class); 262 263 doCallRealMethod().when(service).setService(any(PrintService.class)); 264 when(service.getService()).thenCallRealMethod(); 265 266 if (onCreatePrinterDiscoverySessionCallbacks != null) { 267 doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service) 268 .onCreatePrinterDiscoverySessionCallbacks(); 269 } 270 if (onPrintJobQueued != null) { 271 doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class)); 272 } 273 if (onRequestCancelPrintJob != null) { 274 doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob( 275 any(PrintJob.class)); 276 } 277 278 return service; 279 } 280 281 private static final class CallCounter { 282 private final Object mLock = new Object(); 283 284 private int mCallCount; 285 286 public void call() { 287 synchronized (mLock) { 288 mCallCount++; 289 mLock.notifyAll(); 290 } 291 } 292 293 int getCallCount() { 294 synchronized (mLock) { 295 return mCallCount; 296 } 297 } 298 299 public void waitForCount(int count, long timeoutMillis) throws TimeoutException { 300 synchronized (mLock) { 301 final long startTimeMillis = SystemClock.uptimeMillis(); 302 while (mCallCount < count) { 303 try { 304 final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis; 305 final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis; 306 if (remainingTimeMillis <= 0) { 307 throw new TimeoutException(); 308 } 309 mLock.wait(timeoutMillis); 310 } catch (InterruptedException ie) { 311 /* ignore */ 312 } 313 } 314 } 315 } 316 } 317} 318