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