• 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.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
runTest()142     protected void runTest() throws Throwable {
143         // Do nothing if the device does not support printing.
144         if (supportsPrinting()) {
145             super.runTest();
146         }
147     }
148 
149     @Override
setUp()150     public void setUp() throws Exception {
151         super.setUp();
152         if (!supportsPrinting()) {
153             return;
154         }
155 
156         // Make sure we start with a clean slate.
157         clearPrintSpoolerData();
158         enablePrintServices();
159         disableImes();
160 
161         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
162         // Dexmaker is used by mockito.
163         System.setProperty("dexmaker.dexcache", getInstrumentation()
164                 .getTargetContext().getCacheDir().getPath());
165 
166         // Set to US locale.
167         Resources resources = getInstrumentation().getTargetContext().getResources();
168         Configuration oldConfiguration = resources.getConfiguration();
169         if (!oldConfiguration.locale.equals(Locale.US)) {
170             mOldLocale = oldConfiguration.locale;
171             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
172             Configuration newConfiguration = new Configuration(oldConfiguration);
173             newConfiguration.locale = Locale.US;
174             resources.updateConfiguration(newConfiguration, displayMetrics);
175         }
176 
177         // Initialize the latches.
178         mCancelOperationCounter = new CallCounter();
179         mLayoutCallCounter = new CallCounter();
180         mFinishCallCounter = new CallCounter();
181         mWriteCallCounter = new CallCounter();
182         mFinishCallCounter = new CallCounter();
183         mPrintJobQueuedCallCounter = new CallCounter();
184         mDestroySessionCallCounter = new CallCounter();
185 
186         // Create the activity for the right locale.
187         createActivity();
188     }
189 
190     @Override
tearDown()191     public void tearDown() throws Exception {
192         if (!supportsPrinting()) {
193             return;
194         }
195 
196         // Done with the activity.
197         getActivity().finish();
198         enableImes();
199 
200         // Restore the locale if needed.
201         if (mOldLocale != null) {
202             Resources resources = getInstrumentation().getTargetContext().getResources();
203             DisplayMetrics displayMetrics = resources.getDisplayMetrics();
204             Configuration newConfiguration = new Configuration(resources.getConfiguration());
205             newConfiguration.locale = mOldLocale;
206             mOldLocale = null;
207             resources.updateConfiguration(newConfiguration, displayMetrics);
208         }
209 
210         disablePrintServices();
211         // Make sure the spooler is cleaned.
212         clearPrintSpoolerData();
213 
214         super.tearDown();
215     }
216 
print(final PrintDocumentAdapter adapter)217     protected void print(final PrintDocumentAdapter adapter) {
218         // Initiate printing as if coming from the app.
219         getInstrumentation().runOnMainSync(new Runnable() {
220             @Override
221             public void run() {
222                 PrintManager printManager = (PrintManager) getActivity()
223                         .getSystemService(Context.PRINT_SERVICE);
224                 printManager.print("Print job", adapter, null);
225             }
226         });
227     }
228 
onCancelOperationCalled()229     protected void onCancelOperationCalled() {
230         mCancelOperationCounter.call();
231     }
232 
onLayoutCalled()233     protected void onLayoutCalled() {
234         mLayoutCallCounter.call();
235     }
236 
getWriteCallCount()237     protected int getWriteCallCount() {
238         return mWriteCallCounter.getCallCount();
239     }
240 
onWriteCalled()241     protected void onWriteCalled() {
242         mWriteCallCounter.call();
243     }
244 
onFinishCalled()245     protected void onFinishCalled() {
246         mFinishCallCounter.call();
247     }
248 
onPrintJobQueuedCalled()249     protected void onPrintJobQueuedCalled() {
250         mPrintJobQueuedCallCounter.call();
251     }
252 
onPrinterDiscoverySessionDestroyCalled()253     protected void onPrinterDiscoverySessionDestroyCalled() {
254         mDestroySessionCallCounter.call();
255     }
256 
waitForCancelOperationCallbackCalled()257     protected void waitForCancelOperationCallbackCalled() {
258         waitForCallbackCallCount(mCancelOperationCounter, 1,
259                 "Did not get expected call to onCancel for the current operation.");
260     }
261 
waitForPrinterDiscoverySessionDestroyCallbackCalled()262     protected void waitForPrinterDiscoverySessionDestroyCallbackCalled() {
263         waitForCallbackCallCount(mDestroySessionCallCounter, 1,
264                 "Did not get expected call to onDestroyPrinterDiscoverySession.");
265     }
266 
waitForServiceOnPrintJobQueuedCallbackCalled()267     protected void waitForServiceOnPrintJobQueuedCallbackCalled() {
268         waitForCallbackCallCount(mPrintJobQueuedCallCounter, 1,
269                 "Did not get expected call to onPrintJobQueued.");
270     }
271 
waitForAdapterFinishCallbackCalled()272     protected void waitForAdapterFinishCallbackCalled() {
273         waitForCallbackCallCount(mFinishCallCounter, 1,
274                 "Did not get expected call to finish.");
275     }
276 
waitForLayoutAdapterCallbackCount(int count)277     protected void waitForLayoutAdapterCallbackCount(int count) {
278         waitForCallbackCallCount(mLayoutCallCounter, count,
279                 "Did not get expected call to layout.");
280     }
281 
waitForWriteAdapterCallback()282     protected void waitForWriteAdapterCallback() {
283         waitForCallbackCallCount(mWriteCallCounter, 1, "Did not get expected call to write.");
284     }
285 
waitForCallbackCallCount(CallCounter counter, int count, String message)286     private void waitForCallbackCallCount(CallCounter counter, int count, String message) {
287         try {
288             counter.waitForCount(count, OPERATION_TIMEOUT);
289         } catch (TimeoutException te) {
290             fail(message);
291         }
292     }
293 
selectPrinter(String printerName)294     protected void selectPrinter(String printerName) throws UiObjectNotFoundException {
295         try {
296             UiObject destinationSpinner = new UiObject(new UiSelector().resourceId(
297                     "com.android.printspooler:id/destination_spinner"));
298             destinationSpinner.click();
299             UiObject printerOption = new UiObject(new UiSelector().text(printerName));
300             printerOption.click();
301         } catch (UiObjectNotFoundException e) {
302             dumpWindowHierarchy();
303             throw new UiObjectNotFoundException(e);
304         }
305     }
306 
changeOrientation(String orientation)307     protected void changeOrientation(String orientation) throws UiObjectNotFoundException {
308         try {
309             UiObject orientationSpinner = new UiObject(new UiSelector().resourceId(
310                     "com.android.printspooler:id/orientation_spinner"));
311             orientationSpinner.click();
312             UiObject orientationOption = new UiObject(new UiSelector().text(orientation));
313             orientationOption.click();
314         } catch (UiObjectNotFoundException e) {
315             dumpWindowHierarchy();
316             throw new UiObjectNotFoundException(e);
317         }
318     }
319 
changeMediaSize(String mediaSize)320     protected void changeMediaSize(String mediaSize) throws UiObjectNotFoundException {
321         try {
322             UiObject mediaSizeSpinner = new UiObject(new UiSelector().resourceId(
323                     "com.android.printspooler:id/paper_size_spinner"));
324             mediaSizeSpinner.click();
325             UiObject mediaSizeOption = new UiObject(new UiSelector().text(mediaSize));
326             mediaSizeOption.click();
327         } catch (UiObjectNotFoundException e) {
328             dumpWindowHierarchy();
329             throw new UiObjectNotFoundException(e);
330         }
331     }
332 
changeColor(String color)333     protected void changeColor(String color) throws UiObjectNotFoundException {
334         try {
335             UiObject colorSpinner = new UiObject(new UiSelector().resourceId(
336                     "com.android.printspooler:id/color_spinner"));
337             colorSpinner.click();
338             UiObject colorOption = new UiObject(new UiSelector().text(color));
339             colorOption.click();
340         } catch (UiObjectNotFoundException e) {
341             dumpWindowHierarchy();
342             throw new UiObjectNotFoundException(e);
343         }
344     }
345 
changeDuplex(String duplex)346     protected void changeDuplex(String duplex) throws UiObjectNotFoundException {
347         try {
348             UiObject duplexSpinner = new UiObject(new UiSelector().resourceId(
349                     "com.android.printspooler:id/duplex_spinner"));
350             duplexSpinner.click();
351             UiObject duplexOption = new UiObject(new UiSelector().text(duplex));
352             duplexOption.click();
353         } catch (UiObjectNotFoundException e) {
354             dumpWindowHierarchy();
355             throw new UiObjectNotFoundException(e);
356         }
357     }
358 
clickPrintButton()359     protected void clickPrintButton() throws UiObjectNotFoundException {
360         try {
361             UiObject printButton = new UiObject(new UiSelector().resourceId(
362                     "com.android.printspooler:id/print_button"));
363             printButton.click();
364         } catch (UiObjectNotFoundException e) {
365             dumpWindowHierarchy();
366             throw new UiObjectNotFoundException(e);
367         }
368     }
369 
dumpWindowHierarchy()370     private void dumpWindowHierarchy() {
371         String name = "print-test-failure-" + System.currentTimeMillis() + ".xml";
372         File file = new File(getActivity().getFilesDir(), name);
373         getUiDevice().dumpWindowHierarchy(file.toString());
374     }
375 
getActivity()376     protected PrintDocumentActivity getActivity() {
377         return mActivity;
378     }
379 
createActivity()380     private void createActivity() {
381         mActivity = launchActivity(
382                 getInstrumentation().getTargetContext().getPackageName(),
383                 PrintDocumentActivity.class, null);
384     }
385 
openPrintOptions()386     protected void openPrintOptions() throws UiObjectNotFoundException {
387         UiObject expandHandle = new UiObject(new UiSelector().resourceId(
388                 "com.android.printspooler:id/expand_collapse_handle"));
389         expandHandle.click();
390     }
391 
clearPrintSpoolerData()392     protected void clearPrintSpoolerData() throws Exception {
393         assertTrue("failed to clear print spooler data",
394                 SystemUtil.runShellCommand(getInstrumentation(),
395                         String.format("pm clear %s", PRINT_SPOOLER_PACKAGE_NAME))
396                             .contains(PM_CLEAR_SUCCESS_OUTPUT));
397     }
398 
enablePrintServices()399     private void enablePrintServices() throws Exception {
400         String pkgName = getInstrumentation().getContext().getPackageName();
401         String enabledServicesValue = String.format("%s/%s:%s/%s",
402                 pkgName, FirstPrintService.class.getCanonicalName(),
403                 pkgName, SecondPrintService.class.getCanonicalName());
404         SystemUtil.runShellCommand(getInstrumentation(),
405                 "settings put secure enabled_print_services " + enabledServicesValue);
406     }
407 
disablePrintServices()408     private void disablePrintServices() throws Exception {
409         SystemUtil.runShellCommand(getInstrumentation(),
410                 "settings put secure enabled_print_services \"\"");
411     }
412 
verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock, PrintAttributes oldAttributes, PrintAttributes newAttributes, final boolean forPreview)413     protected void verifyLayoutCall(InOrder inOrder, PrintDocumentAdapter mock,
414             PrintAttributes oldAttributes, PrintAttributes newAttributes,
415             final boolean forPreview) {
416         inOrder.verify(mock).onLayout(eq(oldAttributes), eq(newAttributes),
417                 any(CancellationSignal.class), any(LayoutResultCallback.class), argThat(
418                         new BaseMatcher<Bundle>() {
419                             @Override
420                             public boolean matches(Object item) {
421                                 Bundle bundle = (Bundle) item;
422                                 return forPreview == bundle.getBoolean(
423                                         PrintDocumentAdapter.EXTRA_PRINT_PREVIEW);
424                             }
425 
426                             @Override
427                             public void describeTo(Description description) {
428                                 /* do nothing */
429                             }
430                         }));
431     }
432 
createMockPrintDocumentAdapter(Answer<Void> layoutAnswer, Answer<Void> writeAnswer, Answer<Void> finishAnswer)433     protected PrintDocumentAdapter createMockPrintDocumentAdapter(Answer<Void> layoutAnswer,
434             Answer<Void> writeAnswer, Answer<Void> finishAnswer) {
435         // Create a mock print adapter.
436         PrintDocumentAdapter adapter = mock(PrintDocumentAdapter.class);
437         if (layoutAnswer != null) {
438             doAnswer(layoutAnswer).when(adapter).onLayout(any(PrintAttributes.class),
439                     any(PrintAttributes.class), any(CancellationSignal.class),
440                     any(LayoutResultCallback.class), any(Bundle.class));
441         }
442         if (writeAnswer != null) {
443             doAnswer(writeAnswer).when(adapter).onWrite(any(PageRange[].class),
444                     any(ParcelFileDescriptor.class), any(CancellationSignal.class),
445                     any(WriteResultCallback.class));
446         }
447         if (finishAnswer != null) {
448             doAnswer(finishAnswer).when(adapter).onFinish();
449         }
450         return adapter;
451     }
452 
453     @SuppressWarnings("unchecked")
createMockPrinterDiscoverySessionCallbacks( Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery, Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking, Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy)454     protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
455             Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
456             Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
457             Answer<Void> onStopPrinterStateTracking, Answer<Void> onDestroy) {
458         PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
459 
460         doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
461         when(callbacks.getSession()).thenCallRealMethod();
462 
463         if (onStartPrinterDiscovery != null) {
464             doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
465                     any(List.class));
466         }
467         if (onStopPrinterDiscovery != null) {
468             doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
469         }
470         if (onValidatePrinters != null) {
471             doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
472                     any(List.class));
473         }
474         if (onStartPrinterStateTracking != null) {
475             doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
476                     any(PrinterId.class));
477         }
478         if (onStopPrinterStateTracking != null) {
479             doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
480                     any(PrinterId.class));
481         }
482         if (onDestroy != null) {
483             doAnswer(onDestroy).when(callbacks).onDestroy();
484         }
485 
486         return callbacks;
487     }
488 
createMockPrintServiceCallbacks( Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks, Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob)489     protected PrintServiceCallbacks createMockPrintServiceCallbacks(
490             Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
491             Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
492         final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
493 
494         doCallRealMethod().when(service).setService(any(PrintService.class));
495         when(service.getService()).thenCallRealMethod();
496 
497         if (onCreatePrinterDiscoverySessionCallbacks != null) {
498             doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
499                     .onCreatePrinterDiscoverySessionCallbacks();
500         }
501         if (onPrintJobQueued != null) {
502             doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
503         }
504         if (onRequestCancelPrintJob != null) {
505             doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
506                     any(PrintJob.class));
507         }
508 
509         return service;
510     }
511 
writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output, int fromIndex, int toIndex)512     protected void writeBlankPages(PrintAttributes constraints, ParcelFileDescriptor output,
513             int fromIndex, int toIndex) throws IOException {
514         PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), constraints);
515         final int pageCount = toIndex - fromIndex + 1;
516         for (int i = 0; i < pageCount; i++) {
517             PdfDocument.Page page = document.startPage(i);
518             document.finishPage(page);
519         }
520         FileOutputStream fos = new FileOutputStream(output.getFileDescriptor());
521         document.writeTo(fos);
522         document.close();
523     }
524 
525     protected final class CallCounter {
526         private final Object mLock = new Object();
527 
528         private int mCallCount;
529 
call()530         public void call() {
531             synchronized (mLock) {
532                 mCallCount++;
533                 mLock.notifyAll();
534             }
535         }
536 
getCallCount()537         public int getCallCount() {
538             synchronized (mLock) {
539                 return mCallCount;
540             }
541         }
542 
waitForCount(int count, long timeoutMillis)543         public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
544             synchronized (mLock) {
545                 final long startTimeMillis = SystemClock.uptimeMillis();
546                 while (mCallCount < count) {
547                     try {
548                         final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
549                         final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
550                         if (remainingTimeMillis <= 0) {
551                             throw new TimeoutException();
552                         }
553                         mLock.wait(timeoutMillis);
554                     } catch (InterruptedException ie) {
555                         /* ignore */
556                     }
557                 }
558             }
559         }
560     }
561 
supportsPrinting()562     protected boolean supportsPrinting() {
563         return getInstrumentation().getContext().getPackageManager()
564                 .hasSystemFeature(PackageManager.FEATURE_PRINTING);
565     }
566 }
567