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