1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.print.cts;
18 
19 import static org.mockito.Matchers.any;
20 import static org.mockito.Matchers.argThat;
21 import static org.mockito.Matchers.eq;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.doCallRealMethod;
24 import static org.mockito.Mockito.mock;
25 import static org.mockito.Mockito.when;
26 
27 import android.content.Context;
28 import android.content.pm.PackageManager;
29 import android.content.res.Configuration;
30 import android.content.res.Resources;
31 import android.cts.util.SystemUtil;
32 import android.graphics.pdf.PdfDocument;
33 import android.os.Bundle;
34 import android.os.CancellationSignal;
35 import android.os.ParcelFileDescriptor;
36 import android.os.SystemClock;
37 import android.print.PageRange;
38 import android.print.PrintAttributes;
39 import android.print.PrintDocumentAdapter;
40 import android.print.PrintDocumentAdapter.LayoutResultCallback;
41 import android.print.PrintDocumentAdapter.WriteResultCallback;
42 import android.print.PrintManager;
43 import android.print.PrinterId;
44 import android.print.cts.services.FirstPrintService;
45 import android.print.cts.services.PrintServiceCallbacks;
46 import android.print.cts.services.PrinterDiscoverySessionCallbacks;
47 import android.print.cts.services.SecondPrintService;
48 import android.print.cts.services.StubbablePrinterDiscoverySession;
49 import android.print.pdf.PrintedPdfDocument;
50 import android.printservice.PrintJob;
51 import android.printservice.PrintService;
52 import android.support.test.uiautomator.UiAutomatorTestCase;
53 import android.support.test.uiautomator.UiObject;
54 import android.support.test.uiautomator.UiObjectNotFoundException;
55 import android.support.test.uiautomator.UiSelector;
56 import android.util.DisplayMetrics;
57 
58 import org.hamcrest.BaseMatcher;
59 import org.hamcrest.Description;
60 import org.mockito.InOrder;
61 import org.mockito.stubbing.Answer;
62 
63 import java.io.BufferedReader;
64 import java.io.File;
65 import java.io.FileInputStream;
66 import java.io.FileOutputStream;
67 import java.io.IOException;
68 import java.io.InputStreamReader;
69 import java.util.ArrayList;
70 import java.util.List;
71 import java.util.Locale;
72 import java.util.concurrent.TimeoutException;
73 
74 /**
75  * This is the base class for print tests.
76  */
77 public abstract class BasePrintTest extends UiAutomatorTestCase {
78 
79     private static final long OPERATION_TIMEOUT = 100000000;
80 
81     private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
82 
83     protected static final String PRINT_JOB_NAME = "Test";
84 
85     private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
86 
87     private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
88 
89     private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
90 
91     private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
92 
93     private PrintDocumentActivity mActivity;
94 
95     private Locale mOldLocale;
96 
97     private CallCounter mCancelOperationCounter;
98     private CallCounter mLayoutCallCounter;
99     private CallCounter mWriteCallCounter;
100     private CallCounter mFinishCallCounter;
101     private CallCounter mPrintJobQueuedCallCounter;
102     private CallCounter mDestroySessionCallCounter;
103 
104     private String[] mEnabledImes;
105 
getEnabledImes()106     private String[] getEnabledImes() throws IOException {
107         List<String> imeList = new ArrayList<>();
108 
109         ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
110                 .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
111         BufferedReader reader = new BufferedReader(
112                 new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())));
113 
114         String line;
115         while ((line = reader.readLine()) != null) {
116             imeList.add(line);
117         }
118 
119         String[] imeArray = new String[imeList.size()];
120         imeList.toArray(imeArray);
121 
122         return imeArray;
123     }
124 
disableImes()125     private void disableImes() throws Exception {
126         mEnabledImes = getEnabledImes();
127         for (String ime : mEnabledImes) {
128             String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
129             SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
130         }
131     }
132 
enableImes()133     private void enableImes() throws Exception {
134         for (String ime : mEnabledImes) {
135             String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
136             SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
137         }
138         mEnabledImes = null;
139     }
140 
141     @Override
setUp()142     public void setUp() throws Exception {
143         // Make sure we start with a clean slate.
144         clearPrintSpoolerData();
145         enablePrintServices();
146         disableImes();
147 
148         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
149         // Dexmaker is used by mockito.
150         System.setProperty("dexmaker.dexcache", getInstrumentation()
151                 .getTargetContext().getCacheDir().getPath());
152 
153         // Set to US locale.
154         Resources resources = getInstrumentation().getTargetContext().getResources();
155         Configuration oldConfiguration = resources.getConfiguration();
156         if (!oldConfiguration.locale.equals(Locale.US)) {
157             mOldLocale = oldConfiguration.locale;
158             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
159             Configuration newConfiguration = new Configuration(oldConfiguration);
160             newConfiguration.locale = Locale.US;
161             resources.updateConfiguration(newConfiguration, displayMetrics);
162         }
163 
164         // Initialize the latches.
165         mCancelOperationCounter = new CallCounter();
166         mLayoutCallCounter = new CallCounter();
167         mFinishCallCounter = new CallCounter();
168         mWriteCallCounter = new CallCounter();
169         mFinishCallCounter = new CallCounter();
170         mPrintJobQueuedCallCounter = new CallCounter();
171         mDestroySessionCallCounter = new CallCounter();
172 
173         // Create the activity for the right locale.
174         createActivity();
175     }
176 
177     @Override
tearDown()178     public void tearDown() throws Exception {
179         // Done with the activity.
180         getActivity().finish();
181         enableImes();
182 
183         // Restore the locale if needed.
184         if (mOldLocale != null) {
185             Resources resources = getInstrumentation().getTargetContext().getResources();
186             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
187             Configuration newConfiguration = new Configuration(resources.getConfiguration());
188             newConfiguration.locale = mOldLocale;
189             mOldLocale = null;
190             resources.updateConfiguration(newConfiguration, displayMetrics);
191         }
192 
193         disablePrintServices();
194         // Make sure the spooler is cleaned.
195         clearPrintSpoolerData();
196     }
197 
print(final PrintDocumentAdapter adapter)198     protected void print(final PrintDocumentAdapter adapter) {
199         // Initiate printing as if coming from the app.
200         getInstrumentation().runOnMainSync(new Runnable() {
201             @Override
202             public void run() {
203                 PrintManager printManager = (PrintManager) getActivity()
204                         .getSystemService(Context.PRINT_SERVICE);
205                 printManager.print("Print job", adapter, null);
206             }
207         });
208     }
209 
onCancelOperationCalled()210     protected void onCancelOperationCalled() {
211         mCancelOperationCounter.call();
212     }
213 
onLayoutCalled()214     protected void onLayoutCalled() {
215         mLayoutCallCounter.call();
216     }
217 
getWriteCallCount()218     protected int getWriteCallCount() {
219         return mWriteCallCounter.getCallCount();
220     }
221 
onWriteCalled()222     protected void onWriteCalled() {
223         mWriteCallCounter.call();
224     }
225 
onFinishCalled()226     protected void onFinishCalled() {
227         mFinishCallCounter.call();
228     }
229 
onPrintJobQueuedCalled()230     protected void onPrintJobQueuedCalled() {
231         mPrintJobQueuedCallCounter.call();
232     }
233 
onPrinterDiscoverySessionDestroyCalled()234     protected void onPrinterDiscoverySessionDestroyCalled() {
235         mDestroySessionCallCounter.call();
236     }
237 
waitForCancelOperationCallbackCalled()238     protected void waitForCancelOperationCallbackCalled() {
239         waitForCallbackCallCount(mCancelOperationCounter, 1,
240                 "Did not get expected call to onCancel for the current operation.");
241     }
242 
waitForPrinterDiscoverySessionDestroyCallbackCalled()243     protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() {
244         waitForCallbackCallCount(mDestroySessionCallCounter, 1,
245                 "Did not get expected call to onDestroyPrinterDiscoverySession.");
246     }
247 
waitForServiceOnPrintJobQueuedCallbackCalled()248     protected void waitForServiceOnPrintJobQueuedCallbackCalled() {
249         waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1,
250                 "Did not get expected call to onPrintJobQueued.");
251     }
252 
waitForAdapterFinishCallbackCalled()253     protected void waitForAdapterFinishCallbackCalled() {
254         waitForCallbackCallCount(mFinishCallCounter, 1,
255                 "Did not get expected call to finish.");
256     }
257 
waitForLayoutAdapterCallbackCount(int count)258     protected void waitForLayoutAdapterCallbackCount(int count) {
259         waitForCallbackCallCount(mLayoutCallCounter, count,
260                 "Did not get expected call to layout.");
261     }
262 
waitForWriteAdapterCallback()263     protected void waitForWriteAdapterCallback() {
264         waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write.");
265     }
266 
waitForCallbackCallCount(CallCounter counter, int count, String message)267     private void waitForCallbackCallCount(CallCounter counter, int count, String message) {
268         try {
269             counter.waitForCount(count, OPERATION_TIMEOUT);
270         } catch (TimeoutException te) {
271             fail(message);
272         }
273     }
274 
selectPrinter(String printerName)275     protected void selectPrinter(String printerName) throws UiObjectNotFoundException {
276         try {
277             UiObject destinationSpinner = new UiObject(new UiSelector().resourceId(
278                     "com.android.printspooler:id/destination_spinner"));
279             destinationSpinner.click();
280             UiObject printerOption = new UiObject(new UiSelector().text(printerName));
281             printerOption.click();
282         } catch (UiObjectNotFoundException e) {
283             dumpWindowHierarchy();
284             throw new UiObjectNotFoundException(e);
285         }
286     }
287 
changeOrientation(String orientation)288     protected void changeOrientation(String orientation) throws UiObjectNotFoundException {
289         try {
290             UiObject orientationSpinner = new UiObject(new UiSelector().resourceId(
291                     "com.android.printspooler:id/orientation_spinner"));
292             orientationSpinner.click();
293             UiObject orientationOption = new UiObject(new UiSelector().text(orientation));
294             orientationOption.click();
295         } catch (UiObjectNotFoundException e) {
296             dumpWindowHierarchy();
297             throw new UiObjectNotFoundException(e);
298         }
299     }
300 
changeMediaSize(String mediaSize)301     protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException {
302         try {
303             UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId(
304                     "com.android.printspooler:id/paper_size_spinner"));
305             mediaSizeSpinner.click();
306             UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize));
307             mediaSizeOption.click();
308         } catch (UiObjectNotFoundException e) {
309             dumpWindowHierarchy();
310             throw new UiObjectNotFoundException(e);
311         }
312     }
313 
changeColor(String color)314     protected void changeColor(String color) throws UiObjectNotFoundException {
315         try {
316             UiObject colorSpinner = new UiObject(new UiSelector().resourceId(
317                     "com.android.printspooler:id/color_spinner"));
318             colorSpinner.click();
319             UiObject colorOption = new UiObject(new UiSelector().text(color));
320             colorOption.click();
321         } catch (UiObjectNotFoundException e) {
322             dumpWindowHierarchy();
323             throw new UiObjectNotFoundException(e);
324         }
325     }
326 
changeDuplex(String duplex)327     protected void changeDuplex(String duplex) throws UiObjectNotFoundException {
328         try {
329             UiObject duplexSpinner = new UiObject(new UiSelector().resourceId(
330                     "com.android.printspooler:id/duplex_spinner"));
331             duplexSpinner.click();
332             UiObject duplexOption = new UiObject(new UiSelector().text(duplex));
333             duplexOption.click();
334         } catch (UiObjectNotFoundException e) {
335             dumpWindowHierarchy();
336             throw new UiObjectNotFoundException(e);
337         }
338     }
339 
clickPrintButton()340     protected void clickPrintButton() throws UiObjectNotFoundException {
341         try {
342             UiObject printButton = new UiObject(new UiSelector().resourceId(
343                     "com.android.printspooler:id/print_button"));
344             printButton.click();
345         } catch (UiObjectNotFoundException e) {
346             dumpWindowHierarchy();
347             throw new UiObjectNotFoundException(e);
348         }
349     }
350 
dumpWindowHierarchy()351     private void dumpWindowHierarchy() {
352         String name = "print-test-failure-" + System.currentTimeMillis() + ".xml";
353         File file = new File(getActivity().getFilesDir(), name);
354         getUiDevice().dumpWindowHierarchy(file.toString());
355     }
356 
getActivity()357     protected PrintDocumentActivity getActivity() {
358         return mActivity;
359     }
360 
createActivity()361     private void createActivity() {
362         mActivity = launchActivity(
363                 getInstrumentation().getTargetContext().getPackageName(),
364                 PrintDocumentActivity.class, null);
365     }
366 
openPrintOptions()367     protected void openPrintOptions() throws UiObjectNotFoundException {
368         UiObject expandHandle = new UiObject(new UiSelector().resourceId(
369                 "com.android.printspooler:id/expand_collapse_handle"));
370         expandHandle.click();
371     }
372 
clearPrintSpoolerData()373     protected void clearPrintSpoolerData() throws Exception {
374         assertTrue("failed to clear print spooler data",
375                 SystemUtil.runShellCommand(getInstrumentation(),
376                         String.format("pm clear %s", PRINT_SPOOLER_PACKAGE_NAME))
377                             .contains(PM_CLEAR_SUCCESS_OUTPUT));
378     }
379 
enablePrintServices()380     private void enablePrintServices() throws Exception {
381         String pkgName = getInstrumentation().getContext().getPackageName();
382         String enabledServicesValue = String.format("%s/%s:%s/%s",
383                 pkgName, FirstPrintService.class.getCanonicalName(),
384                 pkgName, SecondPrintService.class.getCanonicalName());
385         SystemUtil.runShellCommand(getInstrumentation(),
386                 "settings put secure enabled_print_services " + enabledServicesValue);
387     }
388 
disablePrintServices()389     private void disablePrintServices() throws Exception {
390         SystemUtil.runShellCommand(getInstrumentation(),
391                 "settings put secure enabled_print_services \"\"");
392     }
393 
verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)394     protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
395             PrintAttributes oldAttributes, PrintAttributes newAttributes,
396             final boolean forPreview) {
397         inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
398                 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
399                         new BaseMatcher<Bundle>() {
400                             @Override
401                             public boolean matches(Object item) {
402                                 Bundle bundle = (Bundle) item;
403                                 return forPreview == bundle.getBoolean(
404                                         PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
405                             }
406 
407                             @Override
408                             public void describeTo(Description description) {
409                                 /* do nothing */
410                             }
411                         }));
412     }
413 
createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)414     protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
415             Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
416         // Create a mock print adapter.
417         PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
418         if (layoutAnswer != null) {
419             doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
420                     any(PrintAttributes.class), any(CancellationSignal.class),
421                     any(LayoutResultCallback.class), any(Bundle.class));
422         }
423         if (writeAnswer != null) {
424             doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
425                     any(ParcelFileDescriptor.class), any(CancellationSignal.class),
426                     any(WriteResultCallback.class));
427         }
428         if (finishAnswer != null) {
429             doAnswer(finishAnswer).when(adapter).onFinish();
430         }
431         return adapter;
432     }
433 
434     @SuppressWarnings("unchecked")
createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)435     protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
436             Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
437             Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
438             Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) {
439         PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
440 
441         doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
442         when(callbacks.getSession()).thenCallRealMethod();
443 
444         if (onStartPrinterDiscovery != null) {
445             doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
446                     any(List.class));
447         }
448         if (onStopPrinterDiscovery != null) {
449             doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
450         }
451         if (onValidatePrinters != null) {
452             doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
453                     any(List.class));
454         }
455         if (onStartPrinterStateTracking != null) {
456             doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
457                     any(PrinterId.class));
458         }
459         if (onStopPrinterStateTracking != null) {
460             doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
461                     any(PrinterId.class));
462         }
463         if (onDestroy != null) {
464             doAnswer(onDestroy).when(callbacks).onDestroy();
465         }
466 
467         return callbacks;
468     }
469 
createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)470     protected PrintServiceCallbacks createMockPrintServiceCallbacks(
471             Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
472             Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
473         final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
474 
475         doCallRealMethod().when(service).setService(any(PrintService.class));
476         when(service.getService()).thenCallRealMethod();
477 
478         if (onCreatePrinterDiscoverySessionCallbacks != null) {
479             doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
480                     .onCreatePrinterDiscoverySessionCallbacks();
481         }
482         if (onPrintJobQueued != null) {
483             doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
484         }
485         if (onRequestCancelPrintJob != null) {
486             doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
487                     any(PrintJob.class));
488         }
489 
490         return service;
491     }
492 
writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)493     protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
494             int fromIndex, int toIndex) throws IOException {
495         PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
496         final int pageCount = toIndex - fromIndex + 1;
497         for (int i = 0; i < pageCount; i++) {
498             PdfDocument.Page page = document.startPage(i);
499             document.finishPage(page);
500         }
501         FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
502         document.writeTo(fos);
503         document.close();
504     }
505 
506     protected final class CallCounter {
507         private final Object mLock = new Object();
508 
509         private int mCallCount;
510 
call()511         public void call() {
512             synchronized (mLock) {
513                 mCallCount++;
514                 mLock.notifyAll();
515             }
516         }
517 
getCallCount()518         public int getCallCount() {
519             synchronized (mLock) {
520                 return mCallCount;
521             }
522         }
523 
waitForCount(int count, long timeoutMillis)524         public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
525             synchronized (mLock) {
526                 final long startTimeMillis = SystemClock.uptimeMillis();
527                 while (mCallCount < count) {
528                     try {
529                         final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
530                         final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
531                         if (remainingTimeMillis <= 0) {
532                             throw new TimeoutException();
533                         }
534                         mLock.wait(timeoutMillis);
535                     } catch (InterruptedException ie) {
536                         /* ignore */
537                     }
538                 }
539             }
540         }
541     }
542 
supportsPrinting()543     protected boolean supportsPrinting() {
544         return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
545     }
546 }
547