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.test;
18 
19 import static android.content.pm.PackageManager.GET_META_DATA;
20 import static android.content.pm.PackageManager.GET_SERVICES;
21 import static android.print.test.Utils.eventually;
22 import static android.print.test.Utils.getPrintManager;
23 
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 import static org.junit.Assume.assumeTrue;
28 import static org.mockito.Matchers.any;
29 import static org.mockito.Matchers.eq;
30 import static org.mockito.Mockito.doAnswer;
31 import static org.mockito.Mockito.doCallRealMethod;
32 import static org.mockito.Mockito.mock;
33 import static org.mockito.Mockito.when;
34 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
35 
36 import android.app.Activity;
37 import android.app.Instrumentation;
38 import android.app.UiAutomation;
39 import android.content.ComponentName;
40 import android.content.Context;
41 import android.content.Intent;
42 import android.content.pm.PackageManager;
43 import android.content.pm.ResolveInfo;
44 import android.graphics.pdf.PdfDocument;
45 import android.os.Bundle;
46 import android.os.CancellationSignal;
47 import android.os.ParcelFileDescriptor;
48 import android.os.SystemClock;
49 import android.platform.helpers.exceptions.TestHelperException;
50 import android.print.PageRange;
51 import android.print.PrintAttributes;
52 import android.print.PrintDocumentAdapter;
53 import android.print.PrintDocumentAdapter.LayoutResultCallback;
54 import android.print.PrintDocumentAdapter.WriteResultCallback;
55 import android.print.PrintDocumentInfo;
56 import android.print.PrintManager;
57 import android.print.PrinterId;
58 import android.print.pdf.PrintedPdfDocument;
59 import android.print.test.services.PrintServiceCallbacks;
60 import android.print.test.services.PrinterDiscoverySessionCallbacks;
61 import android.print.test.services.StubbablePrintService;
62 import android.print.test.services.StubbablePrinterDiscoverySession;
63 import android.printservice.CustomPrinterIconCallback;
64 import android.printservice.PrintJob;
65 import android.provider.Settings;
66 import android.util.Log;
67 import android.util.SparseArray;
68 
69 import androidx.annotation.NonNull;
70 import androidx.annotation.Nullable;
71 import androidx.test.InstrumentationRegistry;
72 
73 import com.android.compatibility.common.util.SystemUtil;
74 import com.android.cts.helpers.DeviceInteractionHelperRule;
75 import com.android.cts.helpers.ICtsPrintHelper;
76 
77 import org.hamcrest.BaseMatcher;
78 import org.hamcrest.Description;
79 import org.junit.After;
80 import org.junit.AfterClass;
81 import org.junit.Before;
82 import org.junit.BeforeClass;
83 import org.junit.Rule;
84 import org.junit.rules.TestRule;
85 import org.junit.runners.model.Statement;
86 import org.mockito.InOrder;
87 import org.mockito.stubbing.Answer;
88 
89 import java.io.BufferedReader;
90 import java.io.FileInputStream;
91 import java.io.FileOutputStream;
92 import java.io.IOException;
93 import java.io.InputStreamReader;
94 import java.lang.annotation.Annotation;
95 import java.lang.annotation.ElementType;
96 import java.lang.annotation.Retention;
97 import java.lang.annotation.RetentionPolicy;
98 import java.lang.annotation.Target;
99 import java.util.ArrayList;
100 import java.util.List;
101 import java.util.Objects;
102 import java.util.concurrent.TimeoutException;
103 import java.util.concurrent.atomic.AtomicInteger;
104 
105 /**
106  * This is the base class for print tests.
107  */
108 public abstract class BasePrintTest {
109     private static final String LOG_TAG = "BasePrintTest";
110 
111     protected static final long OPERATION_TIMEOUT_MILLIS = 20000;
112     protected static final String PRINT_JOB_NAME = "Test";
113     static final String TEST_ID = "BasePrintTest.EXTRA_TEST_ID";
114 
115     private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
116     private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
117     private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
118     private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
119     private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
120     private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
121     private static final String PRINTSPOOLER_PACKAGE = "com.android.printspooler";
122     private static final long GET_UIAUTOMATION_TIMEOUT_MS = 60000;
123 
124     private static final AtomicInteger sLastTestID = new AtomicInteger();
125     private int mTestId;
126     private PrintDocumentActivity mActivity;
127 
128     private static String sDisabledPrintServicesBefore;
129 
130     private static final SparseArray<BasePrintTest> sIdToTest = new SparseArray<>();
131 
132     public final @Rule ShouldStartActivity mShouldStartActivityRule = new ShouldStartActivity();
133     public final @Rule DeviceInteractionHelperRule<ICtsPrintHelper> mHelperRule =
134             new DeviceInteractionHelperRule(ICtsPrintHelper.class);
135 
136     protected ICtsPrintHelper mPrintHelper;
137 
138     private CallCounter mCancelOperationCounter;
139     private CallCounter mLayoutCallCounter;
140     private CallCounter mWriteCallCounter;
141     private CallCounter mWriteCancelCallCounter;
142     private CallCounter mStartCallCounter;
143     private CallCounter mFinishCallCounter;
144     private CallCounter mPrintJobQueuedCallCounter;
145     private CallCounter mCreateSessionCallCounter;
146     private CallCounter mDestroySessionCallCounter;
147     private CallCounter mDestroyActivityCallCounter = new CallCounter();
148     private CallCounter mCreateActivityCallCounter = new CallCounter();
149 
150     private static String[] sEnabledImes;
151 
getEnabledImes()152     private static String[] getEnabledImes() throws IOException {
153         List<String> imeList = new ArrayList<>();
154 
155         ParcelFileDescriptor pfd = getUiAutomation()
156                 .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
157         try (BufferedReader reader = new BufferedReader(
158                 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
159 
160             String line;
161             while ((line = reader.readLine()) != null) {
162                 imeList.add(line);
163             }
164         }
165 
166         String[] imeArray = new String[imeList.size()];
167         imeList.toArray(imeArray);
168 
169         return imeArray;
170     }
171 
disableImes()172     private static void disableImes() throws Exception {
173         sEnabledImes = getEnabledImes();
174         for (String ime : sEnabledImes) {
175             String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
176             SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
177         }
178     }
179 
enableImes()180     private static void enableImes() throws Exception {
181         for (String ime : sEnabledImes) {
182             String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
183             SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
184         }
185         sEnabledImes = null;
186     }
187 
getInstrumentation()188     public static Instrumentation getInstrumentation() {
189         return InstrumentationRegistry.getInstrumentation();
190     }
191 
getUiAutomation()192     public static UiAutomation getUiAutomation() {
193         long start = SystemClock.uptimeMillis();
194         while (SystemClock.uptimeMillis() - start < GET_UIAUTOMATION_TIMEOUT_MS) {
195             UiAutomation ui = getInstrumentation().getUiAutomation();
196             if (ui != null) {
197                 return ui;
198             }
199         }
200 
201         throw new AssertionError("Failed to get UiAutomation");
202     }
203 
204     @BeforeClass
setUpClass()205     public static void setUpClass() throws Exception {
206         Log.d(LOG_TAG, "setUpClass()");
207 
208         Instrumentation instrumentation = getInstrumentation();
209 
210         // Make sure we start with a clean slate.
211         Log.d(LOG_TAG, "clearPrintSpoolerData()");
212         clearPrintSpoolerData();
213         Log.d(LOG_TAG, "disableImes()");
214         disableImes();
215         Log.d(LOG_TAG, "disablePrintServices()");
216         sDisabledPrintServicesBefore = disablePrintServices(instrumentation.getTargetContext()
217                 .getPackageName());
218 
219         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
220         // Dexmaker is used by mockito.
221         System.setProperty("dexmaker.dexcache", instrumentation
222                 .getTargetContext().getCacheDir().getPath());
223 
224         Log.d(LOG_TAG, "setUpClass() done");
225     }
226 
227     /**
228      * Disable all print services beside the ones we want to leave enabled.
229      *
230      * @param packageToLeaveEnabled The package of the services to leave enabled.
231      *
232      * @return Services that were enabled before this method was called
233      */
disablePrintServices(@ullable String packageToLeaveEnabled)234     protected static @NonNull String disablePrintServices(@Nullable String packageToLeaveEnabled)
235             throws IOException {
236         Instrumentation instrumentation = getInstrumentation();
237 
238         String previousEnabledServices = SystemUtil.runShellCommand(instrumentation,
239                 "settings get secure " + Settings.Secure.DISABLED_PRINT_SERVICES);
240 
241         Intent printServiceIntent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE);
242         List<ResolveInfo> installedServices = instrumentation.getContext().getPackageManager()
243                 .queryIntentServices(printServiceIntent, GET_SERVICES | GET_META_DATA);
244 
245         StringBuilder builder = new StringBuilder();
246         for (ResolveInfo service : installedServices) {
247             if (packageToLeaveEnabled != null
248                     && packageToLeaveEnabled.equals(service.serviceInfo.packageName)) {
249                 continue;
250             }
251             if (builder.length() > 0) {
252                 builder.append(":");
253             }
254             builder.append(new ComponentName(service.serviceInfo.packageName,
255                     service.serviceInfo.name).flattenToString());
256         }
257 
258         SystemUtil.runShellCommand(instrumentation, "settings put secure "
259                 + Settings.Secure.DISABLED_PRINT_SERVICES + " " + builder);
260 
261         return previousEnabledServices;
262     }
263 
264     /**
265      * Revert {@link #disablePrintServices(String)}
266      *
267      * @param servicesToEnable Services that should be enabled
268      */
enablePrintServices(@onNull String servicesToEnable)269     protected static void enablePrintServices(@NonNull String servicesToEnable) throws IOException {
270         SystemUtil.runShellCommand(getInstrumentation(),
271                 "settings put secure " + Settings.Secure.DISABLED_PRINT_SERVICES + " "
272                         + servicesToEnable);
273     }
274 
275     @Before
setUp()276     public void setUp() throws Exception {
277         Log.d(LOG_TAG, "setUp()");
278 
279         assumeTrue(getInstrumentation().getContext().getPackageManager().hasSystemFeature(
280                 PackageManager.FEATURE_PRINTING));
281 
282         // Initialize the latches.
283         Log.d(LOG_TAG, "init counters");
284         mCancelOperationCounter = new CallCounter();
285         mLayoutCallCounter = new CallCounter();
286         mStartCallCounter = new CallCounter();
287         mFinishCallCounter = new CallCounter();
288         mWriteCallCounter = new CallCounter();
289         mWriteCancelCallCounter = new CallCounter();
290         mFinishCallCounter = new CallCounter();
291         mPrintJobQueuedCallCounter = new CallCounter();
292         mCreateSessionCallCounter = new CallCounter();
293         mDestroySessionCallCounter = new CallCounter();
294 
295         mTestId = sLastTestID.incrementAndGet();
296         sIdToTest.put(mTestId, this);
297 
298         // Create the activity if needed
299         if (!mShouldStartActivityRule.mNoActivity) {
300             createActivity();
301         }
302 
303         mPrintHelper = mHelperRule.getHelper();
304         mPrintHelper.setUp();
305 
306         Log.d(LOG_TAG, "setUp() done");
307     }
308 
309     @After
tearDown()310     public void tearDown() throws Exception {
311         Log.d(LOG_TAG, "tearDown()");
312 
313         if (mPrintHelper != null) {
314             mPrintHelper.tearDown();
315         }
316 
317         finishActivity();
318 
319         sIdToTest.remove(mTestId);
320 
321         Log.d(LOG_TAG, "tearDown() done");
322     }
323 
324     @AfterClass
tearDownClass()325     public static void tearDownClass() throws Exception {
326         Log.d(LOG_TAG, "tearDownClass()");
327 
328         Instrumentation instrumentation = getInstrumentation();
329 
330         Log.d(LOG_TAG, "enablePrintServices()");
331         enablePrintServices(sDisabledPrintServicesBefore);
332 
333         Log.d(LOG_TAG, "enableImes()");
334         enableImes();
335 
336         // Make sure the spooler is cleaned, this also un-approves all services
337         Log.d(LOG_TAG, "clearPrintSpoolerData()");
338         clearPrintSpoolerData();
339 
340         SystemUtil.runShellCommand(instrumentation, "settings put secure "
341                     + Settings.Secure.DISABLED_PRINT_SERVICES + " null");
342 
343         Log.d(LOG_TAG, "tearDownClass() done");
344     }
345 
print(@onNull final PrintDocumentAdapter adapter, final PrintAttributes attributes)346     protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
347             final PrintAttributes attributes) {
348         android.print.PrintJob[] printJob = new android.print.PrintJob[1];
349         // Initiate printing as if coming from the app.
350         getInstrumentation().runOnMainSync(() -> {
351             PrintManager printManager = (PrintManager) getActivity()
352                     .getSystemService(Context.PRINT_SERVICE);
353             printJob[0] = printManager.print("Print job", adapter, attributes);
354         });
355 
356         return printJob[0];
357     }
358 
print(PrintDocumentAdapter adapter)359     protected void print(PrintDocumentAdapter adapter) {
360         print(adapter, (PrintAttributes) null);
361     }
362 
print(PrintDocumentAdapter adapter, String printJobName)363     protected void print(PrintDocumentAdapter adapter, String printJobName) {
364         print(adapter, printJobName, null);
365     }
366 
367     /**
368      * Start printing
369      *
370      * @param adapter      Adapter supplying data to print
371      * @param printJobName The name of the print job
372      */
print(@onNull PrintDocumentAdapter adapter, @NonNull String printJobName, @Nullable PrintAttributes attributes)373     protected void print(@NonNull PrintDocumentAdapter adapter, @NonNull String printJobName,
374             @Nullable PrintAttributes attributes) {
375         // Initiate printing as if coming from the app.
376         getInstrumentation()
377                 .runOnMainSync(() -> getPrintManager(getActivity()).print(printJobName, adapter,
378                         attributes));
379     }
380 
onCancelOperationCalled()381     protected void onCancelOperationCalled() {
382         mCancelOperationCounter.call();
383     }
384 
onStartCalled()385     public void onStartCalled() {
386         mStartCallCounter.call();
387     }
388 
onLayoutCalled()389     protected void onLayoutCalled() {
390         mLayoutCallCounter.call();
391     }
392 
getWriteCallCount()393     protected int getWriteCallCount() {
394         return mWriteCallCounter.getCallCount();
395     }
396 
onWriteCalled()397     protected void onWriteCalled() {
398         mWriteCallCounter.call();
399     }
400 
onWriteCancelCalled()401     protected void onWriteCancelCalled() {
402         mWriteCancelCallCounter.call();
403     }
404 
onFinishCalled()405     protected void onFinishCalled() {
406         mFinishCallCounter.call();
407     }
408 
onPrintJobQueuedCalled()409     protected void onPrintJobQueuedCalled() {
410         mPrintJobQueuedCallCounter.call();
411     }
412 
onPrinterDiscoverySessionCreateCalled()413     protected void onPrinterDiscoverySessionCreateCalled() {
414         mCreateSessionCallCounter.call();
415     }
416 
onPrinterDiscoverySessionDestroyCalled()417     protected void onPrinterDiscoverySessionDestroyCalled() {
418         mDestroySessionCallCounter.call();
419     }
420 
waitForCancelOperationCallbackCalled()421     protected void waitForCancelOperationCallbackCalled() {
422         waitForCallbackCallCount(mCancelOperationCounter, 1,
423                 "Did not get expected call to onCancel for the current operation.");
424     }
425 
waitForPrinterDiscoverySessionCreateCallbackCalled()426     protected void waitForPrinterDiscoverySessionCreateCallbackCalled() {
427         waitForCallbackCallCount(mCreateSessionCallCounter, 1,
428                 "Did not get expected call to onCreatePrinterDiscoverySession.");
429     }
430 
waitForPrinterDiscoverySessionDestroyCallbackCalled(int count)431     public void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
432         waitForCallbackCallCount(mDestroySessionCallCounter, count,
433                 "Did not get expected call to onDestroyPrinterDiscoverySession.");
434     }
435 
waitForServiceOnPrintJobQueuedCallbackCalled(int count)436     protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
437         waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
438                 "Did not get expected call to onPrintJobQueued.");
439     }
440 
waitForAdapterStartCallbackCalled()441     protected void waitForAdapterStartCallbackCalled() {
442         waitForCallbackCallCount(mStartCallCounter, 1,
443                 "Did not get expected call to start.");
444     }
445 
waitForAdapterFinishCallbackCalled()446     protected void waitForAdapterFinishCallbackCalled() {
447         waitForCallbackCallCount(mFinishCallCounter, 1,
448                 "Did not get expected call to finish.");
449     }
450 
waitForLayoutAdapterCallbackCount(int count)451     protected void waitForLayoutAdapterCallbackCount(int count) {
452         waitForCallbackCallCount(mLayoutCallCounter, count,
453                 "Did not get expected call to layout.");
454     }
455 
waitForWriteAdapterCallback(int count)456     public void waitForWriteAdapterCallback(int count) {
457         waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
458     }
459 
waitForWriteCancelCallback(int count)460     protected void waitForWriteCancelCallback(int count) {
461         waitForCallbackCallCount(mWriteCancelCallCounter, count,
462                 "Did not get expected cancel of write.");
463     }
464 
waitForCallbackCallCount(CallCounter counter, int count, String message)465     private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
466         try {
467             counter.waitForCount(count, OPERATION_TIMEOUT_MILLIS);
468         } catch (TimeoutException te) {
469             fail(message);
470         }
471     }
472 
473     /**
474      * Indicate the print activity was created.
475      */
onActivityCreateCalled(int testId, PrintDocumentActivity activity)476     static void onActivityCreateCalled(int testId, PrintDocumentActivity activity) {
477         synchronized (sIdToTest) {
478             BasePrintTest test = sIdToTest.get(testId);
479             if (test != null) {
480                 test.mActivity = activity;
481                 test.mCreateActivityCallCounter.call();
482             }
483         }
484     }
485 
486     /**
487      * Indicate the print activity was destroyed.
488      */
onActivityDestroyCalled(int testId)489     static void onActivityDestroyCalled(int testId) {
490         synchronized (sIdToTest) {
491             BasePrintTest test = sIdToTest.get(testId);
492             if (test != null) {
493                 test.mDestroyActivityCallCounter.call();
494             }
495         }
496     }
497 
finishActivity()498     private void finishActivity() {
499         Activity activity = mActivity;
500 
501         if (activity != null) {
502             if (!activity.isFinishing()) {
503                 activity.finish();
504             }
505 
506             while (!activity.isDestroyed()) {
507                 int creates = mCreateActivityCallCounter.getCallCount();
508                 waitForCallbackCallCount(mDestroyActivityCallCounter, creates,
509                         "Activity was not destroyed");
510             }
511         }
512     }
513 
514     /**
515      * Get the number of ties the print activity was destroyed.
516      *
517      * @return The number of onDestroy calls on the print activity.
518      */
getActivityDestroyCallbackCallCount()519     protected int getActivityDestroyCallbackCallCount() {
520         return mDestroyActivityCallCounter.getCallCount();
521     }
522 
523     /**
524      * Get the number of ties the print activity was created.
525      *
526      * @return The number of onCreate calls on the print activity.
527      */
getActivityCreateCallbackCallCount()528     private int getActivityCreateCallbackCallCount() {
529         return mCreateActivityCallCounter.getCallCount();
530     }
531 
532     /**
533      * Wait until create was called {@code count} times.
534      *
535      * @param count The number of create calls to expect.
536      */
waitForActivityCreateCallbackCalled(int count)537     private void waitForActivityCreateCallbackCalled(int count) {
538         waitForCallbackCallCount(mCreateActivityCallCounter, count,
539                 "Did not get expected call to create.");
540     }
541 
542     /**
543      * Reset all counters.
544      */
resetCounters()545     public void resetCounters() {
546         mCancelOperationCounter.reset();
547         mLayoutCallCounter.reset();
548         mWriteCallCounter.reset();
549         mWriteCancelCallCounter.reset();
550         mStartCallCounter.reset();
551         mFinishCallCounter.reset();
552         mPrintJobQueuedCallCounter.reset();
553         mCreateSessionCallCounter.reset();
554         mDestroySessionCallCounter.reset();
555         mDestroyActivityCallCounter.reset();
556         mCreateActivityCallCounter.reset();
557     }
558 
559     /**
560      * Wait until the message is shown that indicates that a printer is unavailable.
561      *
562      * @throws Exception If anything was unexpected.
563      */
waitForPrinterUnavailable()564     protected void waitForPrinterUnavailable() throws Exception {
565         final String printerUnavailableMessage = "This printer isn\'t available right now.";
566 
567         String message = mPrintHelper.getStatusMessage();
568         if (!message.equals(printerUnavailableMessage)) {
569             throw new Exception("Wrong message: " + message + " instead of "
570                     + printerUnavailableMessage);
571         }
572     }
573 
selectPrinter(String printerName)574     protected void selectPrinter(String printerName) throws IOException, TestHelperException {
575         mPrintHelper.selectPrinter(printerName, OPERATION_TIMEOUT_MILLIS);
576     }
577 
selectPrinter(String printerName, long timeout)578     protected void selectPrinter(String printerName, long timeout) throws IOException,
579             TestHelperException {
580         mPrintHelper.selectPrinter(printerName, timeout);
581     }
582 
answerPrintServicesWarning(boolean confirm)583     protected void answerPrintServicesWarning(boolean confirm) throws TestHelperException {
584         mPrintHelper.answerPrintServicesWarning(confirm);
585     }
586 
changeOrientation(String orientation)587     protected void changeOrientation(String orientation) throws TestHelperException {
588         mPrintHelper.setPageOrientation(orientation);
589     }
590 
getOrientation()591     public String getOrientation() throws TestHelperException {
592         return mPrintHelper.getPageOrientation();
593     }
594 
changeMediaSize(String mediaSize)595     protected void changeMediaSize(String mediaSize) throws TestHelperException {
596         mPrintHelper.setMediaSize(mediaSize);
597     }
598 
changeColor(String color)599     protected void changeColor(String color) throws TestHelperException {
600         mPrintHelper.setColorMode(color);
601     }
602 
getColor()603     public String getColor() throws TestHelperException {
604         return mPrintHelper.getColorMode();
605     }
606 
changeDuplex(String duplex)607     protected void changeDuplex(String duplex) throws TestHelperException {
608         mPrintHelper.setDuplexMode(duplex);
609     }
610 
changeCopies(int newCopies)611     protected void changeCopies(int newCopies) throws TestHelperException {
612         mPrintHelper.setCopies(newCopies);
613     }
614 
getCopies()615     protected int getCopies() throws TestHelperException {
616         return mPrintHelper.getCopies();
617     }
618 
assertNoPrintButton()619     protected void assertNoPrintButton() throws TestHelperException {
620         assertFalse(mPrintHelper.canSubmitJob());
621     }
622 
clickRetryButton()623     protected void clickRetryButton() throws TestHelperException {
624         mPrintHelper.retryPrintJob();
625     }
626 
getActivity()627     protected PrintDocumentActivity getActivity() {
628         return mActivity;
629     }
630 
createActivity()631     protected void createActivity() throws IOException {
632         Log.d(LOG_TAG, "createActivity()");
633 
634         int createBefore = getActivityCreateCallbackCallCount();
635 
636         Intent intent = new Intent(Intent.ACTION_MAIN);
637         intent.putExtra(TEST_ID, mTestId);
638 
639         Instrumentation instrumentation = getInstrumentation();
640 
641         // Unlock screen.
642         SystemUtil.runShellCommand(instrumentation, "input keyevent KEYCODE_WAKEUP");
643 
644         intent.setClassName(instrumentation.getTargetContext().getPackageName(),
645                 PrintDocumentActivity.class.getName());
646         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
647         instrumentation.startActivitySync(intent);
648 
649         waitForActivityCreateCallbackCalled(createBefore + 1);
650     }
651 
openPrintOptions()652     protected void openPrintOptions() throws TestHelperException {
653         mPrintHelper.openPrintOptions();
654     }
655 
openCustomPrintOptions()656     protected void openCustomPrintOptions() throws TestHelperException {
657         mPrintHelper.openCustomPrintOptions();
658     }
659 
clearPrintSpoolerData()660     protected static void clearPrintSpoolerData() throws Exception {
661         if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(
662                   PackageManager.FEATURE_PRINTING)) {
663             assertTrue("failed to clear print spooler data",
664                     SystemUtil.runShellCommand(getInstrumentation(), String.format(
665                             "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME))
666                             .contains(PM_CLEAR_SUCCESS_OUTPUT));
667         }
668     }
669 
verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)670     protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
671             PrintAttributes oldAttributes, PrintAttributes newAttributes,
672             final boolean forPreview) {
673         inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
674                 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
675                         new BaseMatcher<Bundle>() {
676                             @Override
677                             public boolean matches(Object item) {
678                                 Bundle bundle = (Bundle) item;
679                                 return forPreview == bundle.getBoolean(
680                                         PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
681                             }
682 
683                             @Override
684                             public void describeTo(Description description) {
685                                 /* do nothing */
686                             }
687                         }));
688     }
689 
createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)690     protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
691             Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
692         // Create a mock print adapter.
693         PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
694         if (layoutAnswer != null) {
695             doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
696                     any(PrintAttributes.class), any(CancellationSignal.class),
697                     any(LayoutResultCallback.class), any(Bundle.class));
698         }
699         if (writeAnswer != null) {
700             doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
701                     any(ParcelFileDescriptor.class), any(CancellationSignal.class),
702                     any(WriteResultCallback.class));
703         }
704         if (finishAnswer != null) {
705             doAnswer(finishAnswer).when(adapter).onFinish();
706         }
707         return adapter;
708     }
709 
710     /**
711      * Create a mock {@link PrintDocumentAdapter} that provides one empty page.
712      *
713      * @return The mock adapter
714      */
createDefaultPrintDocumentAdapter(int numPages)715     public @NonNull PrintDocumentAdapter createDefaultPrintDocumentAdapter(int numPages) {
716         final PrintAttributes[] printAttributes = new PrintAttributes[1];
717 
718         return createMockPrintDocumentAdapter(
719                 invocation -> {
720                     PrintAttributes oldAttributes = (PrintAttributes) invocation.getArguments()[0];
721                     printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
722                     LayoutResultCallback callback =
723                             (LayoutResultCallback) invocation
724                                     .getArguments()[3];
725 
726                     callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
727                             .setPageCount(numPages).build(),
728                             !Objects.equals(oldAttributes, printAttributes[0]));
729 
730                     onLayoutCalled();
731                     return null;
732                 }, invocation -> {
733                     Object[] args = invocation.getArguments();
734                     PageRange[] pages = (PageRange[]) args[0];
735                     ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
736                     WriteResultCallback callback =
737                             (WriteResultCallback) args[3];
738 
739                     writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
740                     fd.close();
741                     callback.onWriteFinished(pages);
742 
743                     onWriteCalled();
744                     return null;
745                 }, invocation -> {
746                     onFinishCalled();
747                     return null;
748                 });
749     }
750 
751     @SuppressWarnings("unchecked")
createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)752     protected static PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
753             Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
754             Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
755             Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
756             Answer<Void> onDestroy) {
757         PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
758 
759         doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
760         when(callbacks.getSession()).thenCallRealMethod();
761 
762         if (onStartPrinterDiscovery != null) {
763             doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
764                     any(List.class));
765         }
766         if (onStopPrinterDiscovery != null) {
767             doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
768         }
769         if (onValidatePrinters != null) {
770             doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
771                     any(List.class));
772         }
773         if (onStartPrinterStateTracking != null) {
774             doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
775                     any(PrinterId.class));
776         }
777         if (onRequestCustomPrinterIcon != null) {
778             doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
779                     any(PrinterId.class), any(CancellationSignal.class),
780                     any(CustomPrinterIconCallback.class));
781         }
782         if (onStopPrinterStateTracking != null) {
783             doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
784                     any(PrinterId.class));
785         }
786         if (onDestroy != null) {
787             doAnswer(onDestroy).when(callbacks).onDestroy();
788         }
789 
790         return callbacks;
791     }
792 
createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)793     protected PrintServiceCallbacks createMockPrintServiceCallbacks(
794             Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
795             Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
796         final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
797 
798         doCallRealMethod().when(service).setService(any(StubbablePrintService.class));
799         when(service.getService()).thenCallRealMethod();
800 
801         if (onCreatePrinterDiscoverySessionCallbacks != null) {
802             doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
803                     .onCreatePrinterDiscoverySessionCallbacks();
804         }
805         if (onPrintJobQueued != null) {
806             doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
807         }
808         if (onRequestCancelPrintJob != null) {
809             doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
810                     any(PrintJob.class));
811         }
812 
813         return service;
814     }
815 
writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)816     protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
817             int fromIndex, int toIndex) throws IOException {
818         PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
819         final int pageCount = toIndex - fromIndex + 1;
820         for (int i = 0; i < pageCount; i++) {
821             PdfDocument.Page page = document.startPage(i);
822             document.finishPage(page);
823         }
824         FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
825         document.writeTo(fos);
826         fos.flush();
827         document.close();
828     }
829 
selectPages(String pages, int totalPages)830     protected void selectPages(String pages, int totalPages) throws Exception {
831         mPrintHelper.setPageRange(pages, totalPages);
832     }
833 
834     private static final class CallCounter {
835         private final Object mLock = new Object();
836 
837         private int mCallCount;
838 
call()839         public void call() {
840             synchronized (mLock) {
841                 mCallCount++;
842                 mLock.notifyAll();
843             }
844         }
845 
getCallCount()846         int getCallCount() {
847             synchronized (mLock) {
848                 return mCallCount;
849             }
850         }
851 
reset()852         public void reset() {
853             synchronized (mLock) {
854                 mCallCount = 0;
855             }
856         }
857 
waitForCount(int count, long timeoutMillis)858         public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
859             synchronized (mLock) {
860                 final long startTimeMillis = SystemClock.uptimeMillis();
861                 while (mCallCount < count) {
862                     try {
863                         final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
864                         final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
865                         if (remainingTimeMillis <= 0) {
866                             throw new TimeoutException();
867                         }
868                         mLock.wait(timeoutMillis);
869                     } catch (InterruptedException ie) {
870                         /* ignore */
871                     }
872                 }
873             }
874         }
875     }
876 
877 
878     /**
879      * Make {@code printerName} the default printer by adding it to the history of printers by
880      * printing once.
881      *
882      * @param adapter The {@link PrintDocumentAdapter} used
883      * @throws Exception If the printer could not be made default
884      */
makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)885     public void makeDefaultPrinter(PrintDocumentAdapter adapter, String printerName)
886             throws Throwable {
887         // Perform a full print operation on the printer
888         Log.d(LOG_TAG, "print");
889         print(adapter);
890         Log.d(LOG_TAG, "waitForWriteAdapterCallback");
891         waitForWriteAdapterCallback(1);
892         Log.d(LOG_TAG, "selectPrinter");
893         selectPrinter(printerName);
894         Log.d(LOG_TAG, "clickPrintButton");
895         mPrintHelper.submitPrintJob();
896 
897         eventually(() -> {
898             Log.d(LOG_TAG, "answerPrintServicesWarning");
899             answerPrintServicesWarning(true);
900             Log.d(LOG_TAG, "waitForPrinterDiscoverySessionDestroyCallbackCalled");
901             waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
902         }, OPERATION_TIMEOUT_MILLIS * 2);
903 
904         // Switch to new activity, which should now use the default printer
905         Log.d(LOG_TAG, "getActivity().finish()");
906         getActivity().finish();
907 
908         createActivity();
909     }
910 
911     /**
912      * Annotation used to signal that a test does not need an activity.
913      */
914     @Retention(RetentionPolicy.RUNTIME)
915     @Target(ElementType.METHOD)
916     protected @interface NoActivity { }
917 
918     /**
919      * Rule that handles the {@link NoActivity} annotation.
920      */
921     private static class ShouldStartActivity implements TestRule {
922         boolean mNoActivity;
923 
924         @Override
apply(Statement base, org.junit.runner.Description description)925         public Statement apply(Statement base, org.junit.runner.Description description) {
926             for (Annotation annotation : description.getAnnotations()) {
927                 if (annotation instanceof NoActivity) {
928                     mNoActivity = true;
929                     break;
930                 }
931             }
932 
933             return base;
934         }
935     }
936 }
937