Home | History | Annotate | Download | only in test
      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      */
    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 
    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 
    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 
    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 
    196     public static Instrumentation getInstrumentation() {
    197         return InstrumentationRegistry.getInstrumentation();
    198     }
    199 
    200     @BeforeClass
    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      */
    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      */
    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
    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
    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
    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 
    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 
    358     protected void print(PrintDocumentAdapter adapter) {
    359         print(adapter, (PrintAttributes) null);
    360     }
    361 
    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      */
    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 
    380     protected void onCancelOperationCalled() {
    381         mCancelOperationCounter.call();
    382     }
    383 
    384     public void onStartCalled() {
    385         mStartCallCounter.call();
    386     }
    387 
    388     protected void onLayoutCalled() {
    389         mLayoutCallCounter.call();
    390     }
    391 
    392     protected int getWriteCallCount() {
    393         return mWriteCallCounter.getCallCount();
    394     }
    395 
    396     protected void onWriteCalled() {
    397         mWriteCallCounter.call();
    398     }
    399 
    400     protected void onWriteCancelCalled() {
    401         mWriteCancelCallCounter.call();
    402     }
    403 
    404     protected void onFinishCalled() {
    405         mFinishCallCounter.call();
    406     }
    407 
    408     protected void onPrintJobQueuedCalled() {
    409         mPrintJobQueuedCallCounter.call();
    410     }
    411 
    412     protected void onPrinterDiscoverySessionCreateCalled() {
    413         mCreateSessionCallCounter.call();
    414     }
    415 
    416     protected void onPrinterDiscoverySessionDestroyCalled() {
    417         mDestroySessionCallCounter.call();
    418     }
    419 
    420     protected void waitForCancelOperationCallbackCalled() {
    421         waitForCallbackCallCount(mCancelOperationCounter, 1,
    422                 "Did not get expected call to onCancel for the current operation.");
    423     }
    424 
    425     protected void waitForPrinterDiscoverySessionCreateCallbackCalled() {
    426         waitForCallbackCallCount(mCreateSessionCallCounter, 1,
    427                 "Did not get expected call to onCreatePrinterDiscoverySession.");
    428     }
    429 
    430     public void waitForPrinterDiscoverySessionDestroyCallbackCalled(int count) {
    431         waitForCallbackCallCount(mDestroySessionCallCounter, count,
    432                 "Did not get expected call to onDestroyPrinterDiscoverySession.");
    433     }
    434 
    435     protected void waitForServiceOnPrintJobQueuedCallbackCalled(int count) {
    436         waitForCallbackCallCount(mPrintJobQueuedCallCounter, count,
    437                 "Did not get expected call to onPrintJobQueued.");
    438     }
    439 
    440     protected void waitForAdapterStartCallbackCalled() {
    441         waitForCallbackCallCount(mStartCallCounter, 1,
    442                 "Did not get expected call to start.");
    443     }
    444 
    445     protected void waitForAdapterFinishCallbackCalled() {
    446         waitForCallbackCallCount(mFinishCallCounter, 1,
    447                 "Did not get expected call to finish.");
    448     }
    449 
    450     protected void waitForLayoutAdapterCallbackCount(int count) {
    451         waitForCallbackCallCount(mLayoutCallCounter, count,
    452                 "Did not get expected call to layout.");
    453     }
    454 
    455     public void waitForWriteAdapterCallback(int count) {
    456         waitForCallbackCallCount(mWriteCallCounter, count, "Did not get expected call to write.");
    457     }
    458 
    459     protected void waitForWriteCancelCallback(int count) {
    460         waitForCallbackCallCount(mWriteCancelCallCounter, count,
    461                 "Did not get expected cancel of write.");
    462     }
    463 
    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      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    579     protected void selectPrinter(String printerName) throws IOException, UiObjectNotFoundException {
    580         selectPrinter(printerName, OPERATION_TIMEOUT_MILLIS);
    581     }
    582 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    732     protected void assertNoPrintButton() throws UiObjectNotFoundException, IOException {
    733         assertFalse(getUiDevice().hasObject(By.res("com.android.printspooler:id/print_button")));
    734     }
    735 
    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 
    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 
    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 
    769     protected PrintDocumentActivity getActivity() {
    770         return mActivity;
    771     }
    772 
    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 
    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 
    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 
    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 
    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 
    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      */
    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")
    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 
    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 
    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 
    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 
   1002         public void call() {
   1003             synchronized (mLock) {
   1004                 mCallCount++;
   1005                 mLock.notifyAll();
   1006             }
   1007         }
   1008 
   1009         int getCallCount() {
   1010             synchronized (mLock) {
   1011                 return mCallCount;
   1012             }
   1013         }
   1014 
   1015         public void reset() {
   1016             synchronized (mLock) {
   1017                 mCallCount = 0;
   1018             }
   1019         }
   1020 
   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      */
   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
   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