1 /*
2  * Copyright (C) 2014 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 org.mockito.Matchers.any;
20 import static org.mockito.Matchers.argThat;
21 import static org.mockito.Matchers.eq;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.doCallRealMethod;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.cts.util.SystemUtil;
32 import android.graphics.pdf.PdfDocument;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.os.LocaleList;
36 import android.os.ParcelFileDescriptor;
37 import android.os.SystemClock;
38 import android.print.PageRange;
39 import android.print.PrintAttributes;
40 import android.print.PrintDocumentAdapter;
41 import android.print.PrintDocumentAdapter.LayoutResultCallback;
42 import android.print.PrintDocumentAdapter.WriteResultCallback;
43 import android.print.PrintManager;
44 import android.print.PrinterId;
45 import android.print.cts.services.PrintServiceCallbacks;
46 import android.print.cts.services.PrinterDiscoverySessionCallbacks;
47 import android.print.cts.services.StubbablePrinterDiscoverySession;
48 import android.print.pdf.PrintedPdfDocument;
49 import android.printservice.CustomPrinterIconCallback;
50 import android.printservice.PrintJob;
51 import android.printservice.PrintService;
52 import android.support.test.uiautomator.By;
53 import android.support.test.uiautomator.UiDevice;
54 import android.support.test.uiautomator.UiObject;
55 import android.support.test.uiautomator.UiObjectNotFoundException;
56 import android.support.test.uiautomator.UiSelector;
57 import android.test.InstrumentationTestCase;
58 import android.util.DisplayMetrics;
59 import android.util.Log;
60 
61 import org.hamcrest.BaseMatcher;
62 import org.hamcrest.Description;
63 import org.mockito.InOrder;
64 import org.mockito.stubbing.Answer;
65 
66 import java.io.BufferedReader;
67 import java.io.ByteArrayOutputStream;
68 import java.io.FileInputStream;
69 import java.io.FileOutputStream;
70 import java.io.IOException;
71 import java.io.InputStreamReader;
72 import java.util.ArrayList;
73 import java.util.List;
74 import java.util.Locale;
75 import java.util.concurrent.TimeoutException;
76 /**
77  * This is the base class for print tests.
78  */
79 public abstract class BasePrintTest extends InstrumentationTestCase {
80     private final static String LOG_TAG = "BasePrintTest";
81 
82     protected static final long OPERATION_TIMEOUT_MILLIS = 60000;
83     private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
84     protected static final String PRINT_JOB_NAME = "Test";
85     private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
86     private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
87     private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
88     private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
89     private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
90 
91     private static PrintDocumentActivity sActivity;
92     private UiDevice mUiDevice;
93 
94     /**
95      * Return the UI device
96      *
97      * @return the UI device
98      */
getUiDevice()99     public UiDevice getUiDevice() {
100         return mUiDevice;
101     }
102 
103     private LocaleList mOldLocale;
104 
105     private CallCounter mCancelOperationCounter;
106     private CallCounter mLayoutCallCounter;
107     private CallCounter mWriteCallCounter;
108     private CallCounter mFinishCallCounter;
109     private CallCounter mPrintJobQueuedCallCounter;
110     private CallCounter mCreateSessionCallCounter;
111     private CallCounter mDestroySessionCallCounter;
112     private static CallCounter sDestroyActivityCallCounter = new CallCounter();
113     private static CallCounter sCreateActivityCallCounter = new CallCounter();
114 
115     private String[] mEnabledImes;
116 
getEnabledImes()117     private String[] getEnabledImes() throws IOException {
118         List<String> imeList = new ArrayList<>();
119 
120         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
121                 .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
122         try (BufferedReader reader = new BufferedReader(
123                 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
124 
125             String line;
126             while ((line = reader.readLine()) != null) {
127                 imeList.add(line);
128             }
129         }
130 
131         String[] imeArray = new String[imeList.size()];
132         imeList.toArray(imeArray);
133 
134         return imeArray;
135     }
136 
disableImes()137     private void disableImes() throws Exception {
138         mEnabledImes = getEnabledImes();
139         for (String ime : mEnabledImes) {
140             String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
141             SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
142         }
143     }
144 
enableImes()145     private void enableImes() throws Exception {
146         for (String ime : mEnabledImes) {
147             String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
148             SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
149         }
150         mEnabledImes = null;
151     }
152 
153     @Override
runTest()154     protected void runTest() throws Throwable {
155         // Do nothing if the device does not support printing.
156         if (supportsPrinting()) {
157             super.runTest();
158         }
159     }
160 
161     @Override
setUp()162     public void setUp() throws Exception {
163         Log.d(LOG_TAG, "setUp()");
164 
165         super.setUp();
166         if (!supportsPrinting()) {
167             return;
168         }
169 
170         mUiDevice = UiDevice.getInstance(getInstrumentation());
171 
172         // Make sure we start with a clean slate.
173         Log.d(LOG_TAG, "clearPrintSpoolerData()");
174         clearPrintSpoolerData();
175         Log.d(LOG_TAG, "disableImes()");
176         disableImes();
177 
178         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
179         // Dexmaker is used by mockito.
180         System.setProperty("dexmaker.dexcache", getInstrumentation()
181                 .getTargetContext().getCacheDir().getPath());
182 
183         // Set to US locale.
184         Log.d(LOG_TAG, "set locale");
185         Resources resources = getInstrumentation().getTargetContext().getResources();
186         Configuration oldConfiguration = resources.getConfiguration();
187         if (!oldConfiguration.getLocales().get(0).equals(Locale.US)) {
188             mOldLocale = oldConfiguration.getLocales();
189             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
190             Configuration newConfiguration = new Configuration(oldConfiguration);
191             newConfiguration.setLocale(Locale.US);
192             resources.updateConfiguration(newConfiguration, displayMetrics);
193         }
194 
195         // Initialize the latches.
196         Log.d(LOG_TAG, "init counters");
197         mCancelOperationCounter = new CallCounter();
198         mLayoutCallCounter = new CallCounter();
199         mFinishCallCounter = new CallCounter();
200         mWriteCallCounter = new CallCounter();
201         mFinishCallCounter = new CallCounter();
202         mPrintJobQueuedCallCounter = new CallCounter();
203         mCreateSessionCallCounter = new CallCounter();
204         mDestroySessionCallCounter = new CallCounter();
205 
206         // Create the activity for the right locale.
207         Log.d(LOG_TAG, "createActivity()");
208         createActivity();
209         Log.d(LOG_TAG, "setUp() done");
210     }
211 
212     @Override
tearDown()213     public void tearDown() throws Exception {
214         Log.d(LOG_TAG, "tearDown()");
215 
216         if (!supportsPrinting()) {
217             return;
218         }
219 
220         // Done with the activity.
221         Log.d(LOG_TAG, "finish activity");
222         if (!getActivity().isFinishing()) {
223             getActivity().finish();
224         }
225 
226         Log.d(LOG_TAG, "enableImes()");
227         enableImes();
228 
229         // Restore the locale if needed.
230         Log.d(LOG_TAG, "restore locale");
231         if (mOldLocale != null) {
232             Resources resources = getInstrumentation().getTargetContext().getResources();
233             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
234             Configuration newConfiguration = new Configuration(resources.getConfiguration());
235             newConfiguration.setLocales(mOldLocale);
236             mOldLocale = null;
237             resources.updateConfiguration(newConfiguration, displayMetrics);
238         }
239 
240         // Make sure the spooler is cleaned, this also un-approves all services
241         Log.d(LOG_TAG, "clearPrintSpoolerData()");
242         clearPrintSpoolerData();
243 
244         super.tearDown();
245         Log.d(LOG_TAG, "tearDown() done");
246     }
247 
print(final PrintDocumentAdapter adapter, final PrintAttributes attributes)248     protected void print(final PrintDocumentAdapter adapter, final PrintAttributes attributes) {
249         // Initiate printing as if coming from the app.
250         getInstrumentation().runOnMainSync(new Runnable() {
251             @Override
252             public void run() {
253                 PrintManager printManager = (PrintManager) getActivity()
254                         .getSystemService(Context.PRINT_SERVICE);
255                 printManager.print("Print job", adapter, attributes);
256             }
257         });
258     }
259 
print(PrintDocumentAdapter adapter)260     protected void print(PrintDocumentAdapter adapter) {
261         print(adapter, null);
262     }
263 
onCancelOperationCalled()264     protected void onCancelOperationCalled() {
265         mCancelOperationCounter.call();
266     }
267 
onLayoutCalled()268     protected void onLayoutCalled() {
269         mLayoutCallCounter.call();
270     }
271 
getWriteCallCount()272     protected int getWriteCallCount() {
273         return mWriteCallCounter.getCallCount();
274     }
275 
onWriteCalled()276     protected void onWriteCalled() {
277         mWriteCallCounter.call();
278     }
279 
onFinishCalled()280     protected void onFinishCalled() {
281         mFinishCallCounter.call();
282     }
283 
onPrintJobQueuedCalled()284     protected void onPrintJobQueuedCalled() {
285         mPrintJobQueuedCallCounter.call();
286     }
287 
onPrinterDiscoverySessionCreateCalled()288     protected void onPrinterDiscoverySessionCreateCalled() {
289         mCreateSessionCallCounter.call();
290     }
291 
onPrinterDiscoverySessionDestroyCalled()292     protected void onPrinterDiscoverySessionDestroyCalled() {
293         mDestroySessionCallCounter.call();
294     }
295 
waitForCancelOperationCallbackCalled()296     protected void waitForCancelOperationCallbackCalled() {
297         waitForCallbackCallCount(mCancelOperationCounter, 1,
298                 "Did not get expected call to onCancel for the current operation.");
299     }
300 
waitForPrinterDiscoverySessionCreateCallbackCalled()301     protected void waitForPrinterDiscoverySessionCreateCallbackCalled() {
302         waitForCallbackCallCount(mCreateSessionCallCounter, 1,
303                 "Did not get expected call to onCreatePrinterDiscoverySession.");
304     }
305 
waitForPrinterDiscoverySessionDestroyCallbackCalled(int count)306     protected void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
307         waitForCallbackCallCount(mDestroySessionCallCounter, count,
308                 "Did not get expected call to onDestroyPrinterDiscoverySession.");
309     }
310 
waitForServiceOnPrintJobQueuedCallbackCalled(int count)311     protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
312         waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
313                 "Did not get expected call to onPrintJobQueued.");
314     }
315 
waitForAdapterFinishCallbackCalled()316     protected void waitForAdapterFinishCallbackCalled() {
317         waitForCallbackCallCount(mFinishCallCounter, 1,
318                 "Did not get expected call to finish.");
319     }
320 
waitForLayoutAdapterCallbackCount(int count)321     protected void waitForLayoutAdapterCallbackCount(int count) {
322         waitForCallbackCallCount(mLayoutCallCounter, count,
323                 "Did not get expected call to layout.");
324     }
325 
waitForWriteAdapterCallback(int count)326     protected void waitForWriteAdapterCallback(int count) {
327         waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
328     }
329 
waitForCallbackCallCount(CallCounter counter, int count, String message)330     private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
331         try {
332             counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
333         } catch (TimeoutException te) {
334             fail(message);
335         }
336     }
337 
338     /**
339      * Indicate the print activity was created.
340      */
onActivityCreateCalled(PrintDocumentActivity activity)341     static void onActivityCreateCalled(PrintDocumentActivity activity) {
342         sActivity = activity;
343         sCreateActivityCallCounter.call();
344     }
345 
346     /**
347      * Indicate the print activity was destroyed.
348      */
onActivityDestroyCalled()349     static void onActivityDestroyCalled() {
350         sDestroyActivityCallCounter.call();
351     }
352 
353     /**
354      * Get the number of ties the print activity was destroyed.
355      *
356      * @return The number of onDestroy calls on the print activity.
357      */
getActivityDestroyCallbackCallCount()358     protected static int getActivityDestroyCallbackCallCount() {
359         return sDestroyActivityCallCounter.getCallCount();
360     }
361 
362     /**
363      * Get the number of ties the print activity was created.
364      *
365      * @return The number of onCreate calls on the print activity.
366      */
getActivityCreateCallbackCallCount()367     protected static int getActivityCreateCallbackCallCount() {
368         return sCreateActivityCallCounter.getCallCount();
369     }
370 
371     /**
372      * Wait until create was called {@code count} times.
373      *
374      * @param count The number of create calls to expect.
375      */
waitForActivityCreateCallbackCalled(int count)376     private static void waitForActivityCreateCallbackCalled(int count) {
377         waitForCallbackCallCount(sCreateActivityCallCounter, count,
378                 "Did not get expected call to create.");
379     }
380 
381     /**
382      * Reset all counters.
383      */
resetCounters()384     protected void resetCounters() {
385         mCancelOperationCounter.reset();
386         mLayoutCallCounter.reset();
387         mWriteCallCounter.reset();
388         mFinishCallCounter.reset();
389         mPrintJobQueuedCallCounter.reset();
390         mCreateSessionCallCounter.reset();
391         mDestroySessionCallCounter.reset();
392         sDestroyActivityCallCounter.reset();
393         sCreateActivityCallCounter.reset();
394     }
395 
selectPrinter(String printerName)396     protected void selectPrinter(String printerName) throws UiObjectNotFoundException, IOException {
397         try {
398             long delay = 100;
399             while (true) {
400                 try {
401                     UiObject destinationSpinner = mUiDevice.findObject(new UiSelector().resourceId(
402                             "com.android.printspooler:id/destination_spinner"));
403 
404                     destinationSpinner.click();
405 
406                     // Give spinner some time to expand
407                     try {
408                         Thread.sleep(delay);
409                     } catch (InterruptedException e) {
410                         // ignore
411                     }
412 
413                     // try to select printer
414                     UiObject printerOption = mUiDevice
415                             .findObject(new UiSelector().text(printerName));
416                     printerOption.click();
417                 } catch (UiObjectNotFoundException e) {
418                     Log.e(LOG_TAG, "Could not select printer " + printerName, e);
419                 }
420 
421                 // Make sure printer is selected
422                 if (getUiDevice().hasObject(By.text(printerName))) {
423                     break;
424                 } else {
425                     if (delay <= OPERATION_TIMEOUT_MILLIS) {
426                         Log.w(LOG_TAG, "Cannot find printer " + printerName + ", retrying.");
427                         delay *= 2;
428                         continue;
429                     } else {
430                         throw new UiObjectNotFoundException("Could find printer " + printerName +
431                                 " even though we retried");
432                     }
433                 }
434             }
435         } catch (UiObjectNotFoundException e) {
436             dumpWindowHierarchy();
437             throw e;
438         }
439     }
440 
answerPrintServicesWarning(boolean confirm)441     protected void answerPrintServicesWarning(boolean confirm) throws UiObjectNotFoundException {
442         UiDevice uiDevice = UiDevice.getInstance(getInstrumentation());
443         UiObject button;
444         if (confirm) {
445             button = uiDevice.findObject(new UiSelector().resourceId("android:id/button1"));
446         } else {
447             button = uiDevice.findObject(new UiSelector().resourceId("android:id/button2"));
448         }
449         button.click();
450     }
451 
changeOrientation(String orientation)452     protected void changeOrientation(String orientation)
453             throws UiObjectNotFoundException, IOException {
454         try {
455             UiObject orientationSpinner = mUiDevice.findObject(new UiSelector().resourceId(
456                     "com.android.printspooler:id/orientation_spinner"));
457             orientationSpinner.click();
458             UiObject orientationOption = mUiDevice.findObject(new UiSelector().text(orientation));
459             orientationOption.click();
460         } catch (UiObjectNotFoundException e) {
461             dumpWindowHierarchy();
462             throw e;
463         }
464     }
465 
getOrientation()466     protected String getOrientation() throws UiObjectNotFoundException, IOException {
467         try {
468             UiObject orientationSpinner = mUiDevice.findObject(new UiSelector().resourceId(
469                     "com.android.printspooler:id/orientation_spinner"));
470             return orientationSpinner.getText();
471         } catch (UiObjectNotFoundException e) {
472             dumpWindowHierarchy();
473             throw e;
474         }
475     }
476 
changeMediaSize(String mediaSize)477     protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException, IOException {
478         try {
479             UiObject mediaSizeSpinner = mUiDevice.findObject(new UiSelector().resourceId(
480                     "com.android.printspooler:id/paper_size_spinner"));
481             mediaSizeSpinner.click();
482             UiObject mediaSizeOption = mUiDevice.findObject(new UiSelector().text(mediaSize));
483             mediaSizeOption.click();
484         } catch (UiObjectNotFoundException e) {
485             dumpWindowHierarchy();
486             throw e;
487         }
488     }
489 
getMediaSize()490     protected String getMediaSize() throws UiObjectNotFoundException, IOException {
491         try {
492             UiObject mediaSizeSpinner = mUiDevice.findObject(new UiSelector().resourceId(
493                     "com.android.printspooler:id/paper_size_spinner"));
494             return mediaSizeSpinner.getText();
495         } catch (UiObjectNotFoundException e) {
496             dumpWindowHierarchy();
497             throw e;
498         }
499     }
500 
changeColor(String color)501     protected void changeColor(String color) throws UiObjectNotFoundException, IOException {
502         try {
503             UiObject colorSpinner = mUiDevice.findObject(new UiSelector().resourceId(
504                     "com.android.printspooler:id/color_spinner"));
505             colorSpinner.click();
506             UiObject colorOption = mUiDevice.findObject(new UiSelector().text(color));
507             colorOption.click();
508         } catch (UiObjectNotFoundException e) {
509             dumpWindowHierarchy();
510             throw e;
511         }
512     }
513 
getColor()514     protected String getColor() throws UiObjectNotFoundException, IOException {
515         try {
516             UiObject colorSpinner = mUiDevice.findObject(new UiSelector().resourceId(
517                     "com.android.printspooler:id/color_spinner"));
518             return colorSpinner.getText();
519         } catch (UiObjectNotFoundException e) {
520             dumpWindowHierarchy();
521             throw e;
522         }
523     }
524 
changeDuplex(String duplex)525     protected void changeDuplex(String duplex) throws UiObjectNotFoundException, IOException {
526         try {
527             UiObject duplexSpinner = mUiDevice.findObject(new UiSelector().resourceId(
528                     "com.android.printspooler:id/duplex_spinner"));
529             duplexSpinner.click();
530             UiObject duplexOption = mUiDevice.findObject(new UiSelector().text(duplex));
531             duplexOption.click();
532         } catch (UiObjectNotFoundException e) {
533             dumpWindowHierarchy();
534             throw e;
535         }
536     }
537 
getDuplex()538     protected String getDuplex() throws UiObjectNotFoundException, IOException {
539         try {
540             UiObject duplexSpinner = mUiDevice.findObject(new UiSelector().resourceId(
541                     "com.android.printspooler:id/duplex_spinner"));
542             return duplexSpinner.getText();
543         } catch (UiObjectNotFoundException e) {
544             dumpWindowHierarchy();
545             throw e;
546         }
547     }
548 
getCopies()549     protected String getCopies() throws UiObjectNotFoundException, IOException {
550         try {
551             UiObject copies = mUiDevice.findObject(new UiSelector().resourceId(
552                     "com.android.printspooler:id/copies_edittext"));
553             return copies.getText();
554         } catch (UiObjectNotFoundException e) {
555             dumpWindowHierarchy();
556             throw e;
557         }
558     }
559 
clickPrintButton()560     protected void clickPrintButton() throws UiObjectNotFoundException, IOException {
561         try {
562             UiObject printButton = mUiDevice.findObject(new UiSelector().resourceId(
563                     "com.android.printspooler:id/print_button"));
564             printButton.click();
565         } catch (UiObjectNotFoundException e) {
566             dumpWindowHierarchy();
567             throw e;
568         }
569     }
570 
dumpWindowHierarchy()571     protected void dumpWindowHierarchy() throws IOException {
572         ByteArrayOutputStream os = new ByteArrayOutputStream();
573         mUiDevice.dumpWindowHierarchy(os);
574 
575         Log.w(LOG_TAG, "Window hierarchy:");
576         for (String line : os.toString("UTF-8").split("\n")) {
577             Log.w(LOG_TAG, line);
578         }
579     }
580 
getActivity()581     protected PrintDocumentActivity getActivity() {
582         return sActivity;
583     }
584 
createActivity()585     protected void createActivity() {
586         int createBefore = getActivityCreateCallbackCallCount();
587 
588         launchActivity(getInstrumentation().getTargetContext().getPackageName(),
589                 PrintDocumentActivity.class, null);
590 
591         waitForActivityCreateCallbackCalled(createBefore + 1);
592     }
593 
openPrintOptions()594     protected void openPrintOptions() throws UiObjectNotFoundException {
595         UiObject expandHandle = mUiDevice.findObject(new UiSelector().resourceId(
596                 "com.android.printspooler:id/expand_collapse_handle"));
597         expandHandle.click();
598     }
599 
openCustomPrintOptions()600     protected void openCustomPrintOptions() throws UiObjectNotFoundException {
601         UiObject expandHandle = mUiDevice.findObject(new UiSelector().resourceId(
602                 "com.android.printspooler:id/more_options_button"));
603         expandHandle.click();
604     }
605 
clearPrintSpoolerData()606     protected void clearPrintSpoolerData() throws Exception {
607         assertTrue("failed to clear print spooler data",
608                 SystemUtil.runShellCommand(getInstrumentation(), String.format(
609                         "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
610                         .contains(PM_CLEAR_SUCCESS_OUTPUT));
611     }
612 
verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)613     protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
614             PrintAttributes oldAttributes, PrintAttributes newAttributes,
615             final boolean forPreview) {
616         inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
617                 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
618                         new BaseMatcher<Bundle>() {
619                             @Override
620                             public boolean matches(Object item) {
621                                 Bundle bundle = (Bundle) item;
622                                 return forPreview == bundle.getBoolean(
623                                         PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
624                             }
625 
626                             @Override
627                             public void describeTo(Description description) {
628                                 /* do nothing */
629                             }
630                         }));
631     }
632 
createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)633     protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
634             Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
635         // Create a mock print adapter.
636         PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
637         if (layoutAnswer != null) {
638             doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
639                     any(PrintAttributes.class), any(CancellationSignal.class),
640                     any(LayoutResultCallback.class), any(Bundle.class));
641         }
642         if (writeAnswer != null) {
643             doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
644                     any(ParcelFileDescriptor.class), any(CancellationSignal.class),
645                     any(WriteResultCallback.class));
646         }
647         if (finishAnswer != null) {
648             doAnswer(finishAnswer).when(adapter).onFinish();
649         }
650         return adapter;
651     }
652 
653     @SuppressWarnings("unchecked")
createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)654     protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
655             Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
656             Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
657             Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
658             Answer<Void> onDestroy) {
659         PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
660 
661         doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
662         when(callbacks.getSession()).thenCallRealMethod();
663 
664         if (onStartPrinterDiscovery != null) {
665             doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
666                     any(List.class));
667         }
668         if (onStopPrinterDiscovery != null) {
669             doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
670         }
671         if (onValidatePrinters != null) {
672             doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
673                     any(List.class));
674         }
675         if (onStartPrinterStateTracking != null) {
676             doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
677                     any(PrinterId.class));
678         }
679         if (onRequestCustomPrinterIcon != null) {
680             doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
681                     any(PrinterId.class), any(CancellationSignal.class),
682                     any(CustomPrinterIconCallback.class));
683         }
684         if (onStopPrinterStateTracking != null) {
685             doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
686                     any(PrinterId.class));
687         }
688         if (onDestroy != null) {
689             doAnswer(onDestroy).when(callbacks).onDestroy();
690         }
691 
692         return callbacks;
693     }
694 
createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)695     protected PrintServiceCallbacks createMockPrintServiceCallbacks(
696             Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
697             Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
698         final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
699 
700         doCallRealMethod().when(service).setService(any(PrintService.class));
701         when(service.getService()).thenCallRealMethod();
702 
703         if (onCreatePrinterDiscoverySessionCallbacks != null) {
704             doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
705                     .onCreatePrinterDiscoverySessionCallbacks();
706         }
707         if (onPrintJobQueued != null) {
708             doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
709         }
710         if (onRequestCancelPrintJob != null) {
711             doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
712                     any(PrintJob.class));
713         }
714 
715         return service;
716     }
717 
writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)718     protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
719             int fromIndex, int toIndex) throws IOException {
720         PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
721         final int pageCount = toIndex - fromIndex + 1;
722         for (int i = 0; i < pageCount; i++) {
723             PdfDocument.Page page = document.startPage(i);
724             document.finishPage(page);
725         }
726         FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
727         document.writeTo(fos);
728         document.close();
729     }
730 
731     protected static final class CallCounter {
732         private final Object mLock = new Object();
733 
734         private int mCallCount;
735 
call()736         public void call() {
737             synchronized (mLock) {
738                 mCallCount++;
739                 mLock.notifyAll();
740             }
741         }
742 
getCallCount()743         public int getCallCount() {
744             synchronized (mLock) {
745                 return mCallCount;
746             }
747         }
748 
reset()749         public void reset() {
750             synchronized (mLock) {
751                 mCallCount = 0;
752             }
753         }
754 
waitForCount(int count, long timeoutMillis)755         public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
756             synchronized (mLock) {
757                 final long startTimeMillis = SystemClock.uptimeMillis();
758                 while (mCallCount < count) {
759                     try {
760                         final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
761                         final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
762                         if (remainingTimeMillis <= 0) {
763                             throw new TimeoutException();
764                         }
765                         mLock.wait(timeoutMillis);
766                     } catch (InterruptedException ie) {
767                         /* ignore */
768                     }
769                 }
770             }
771         }
772     }
773 
774 
775     /**
776      * Make {@code printerName} the default printer by adding it to the history of printers by
777      * printing once.
778      *
779      * @param adapter The {@link PrintDocumentAdapter} used
780      * @throws Exception If the printer could not be made default
781      */
makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)782     protected void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
783             throws Exception {
784         // Perform a full print operation on the printer
785         Log.d(LOG_TAG, "print");
786         print(adapter);
787         Log.d(LOG_TAG, "waitForWriteAdapterCallback");
788         waitForWriteAdapterCallback(1);
789         Log.d(LOG_TAG, "selectPrinter");
790         selectPrinter(printerName);
791         Log.d(LOG_TAG, "clickPrintButton");
792         clickPrintButton();
793         Log.d(LOG_TAG, "answerPrintServicesWarning");
794         answerPrintServicesWarning(true);
795         Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
796         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
797 
798         // Switch to new activity, which should now use the default printer
799         Log.d(LOG_TAG, "getActivity().finish()");
800         getActivity().finish();
801 
802         Log.d(LOG_TAG, "createActivity");
803         createActivity();
804     }
805 
supportsPrinting()806     protected boolean supportsPrinting() {
807         return getInstrumentation().getContext().getPackageManager()
808                 .hasSystemFeature(PackageManager.FEATURE_PRINTING);
809     }
810 }
811