• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.printspooler.outofprocess.tests;
18 
19 import static org.junit.Assert.assertNotNull;
20 
21 import android.graphics.pdf.PdfDocument;
22 import android.os.Bundle;
23 import android.os.CancellationSignal;
24 import android.os.ParcelFileDescriptor;
25 import android.print.PageRange;
26 import android.print.PrintAttributes;
27 import android.print.PrintDocumentAdapter;
28 import android.print.PrintDocumentInfo;
29 import android.print.PrinterCapabilitiesInfo;
30 import android.print.PrinterId;
31 import android.print.PrinterInfo;
32 import android.print.pdf.PrintedPdfDocument;
33 import android.print.test.BasePrintTest;
34 import android.print.test.services.AddPrintersActivity;
35 import android.print.test.services.FirstPrintService;
36 import android.print.test.services.PrinterDiscoverySessionCallbacks;
37 import android.print.test.services.StubbablePrinterDiscoverySession;
38 import android.support.test.uiautomator.By;
39 import android.support.test.uiautomator.UiDevice;
40 import android.support.test.uiautomator.UiObject;
41 import android.support.test.uiautomator.UiObjectNotFoundException;
42 import android.support.test.uiautomator.UiSelector;
43 import android.support.test.uiautomator.Until;
44 import android.util.Log;
45 
46 import androidx.test.filters.LargeTest;
47 
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.junit.runners.Parameterized;
51 
52 import java.io.FileOutputStream;
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.List;
57 import java.util.concurrent.TimeoutException;
58 import java.util.function.Supplier;
59 
60 /**
61  * Tests for the basic printing workflows
62  */
63 @RunWith(Parameterized.class)
64 public class WorkflowTest extends BasePrintTest {
65     private static final String LOG_TAG = WorkflowTest.class.getSimpleName();
66 
67     private PrintAttributes.MediaSize mFirst;
68     private boolean mSelectPrinter;
69     private PrintAttributes.MediaSize mSecond;
70 
WorkflowTest(PrintAttributes.MediaSize first, boolean selectPrinter, PrintAttributes.MediaSize second)71     public WorkflowTest(PrintAttributes.MediaSize first, boolean selectPrinter,
72             PrintAttributes.MediaSize second) {
73         mFirst = first;
74         mSelectPrinter = selectPrinter;
75         mSecond = second;
76     }
77 
78     interface InterruptableConsumer<T> {
accept(T t)79         void accept(T t) throws InterruptedException;
80     }
81 
getUiDevice()82     public static UiDevice getUiDevice() {
83         return UiDevice.getInstance(getInstrumentation());
84     }
85 
86     /**
87      * Execute {@code waiter} until {@code condition} is met.
88      *
89      * @param condition Conditions to wait for
90      * @param waiter    Code to execute while waiting
91      */
waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter)92     private void waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter)
93             throws TimeoutException, InterruptedException {
94         long startTime = System.currentTimeMillis();
95         while (condition.get()) {
96             long timeLeft = OPERATION_TIMEOUT_MILLIS - (System.currentTimeMillis() - startTime);
97             if (timeLeft < 0) {
98                 throw new TimeoutException();
99             }
100 
101             waiter.accept(timeLeft);
102         }
103     }
104 
105     /** Add a printer with a given name and supported mediasize to a session */
addPrinter(StubbablePrinterDiscoverySession session, String name, PrintAttributes.MediaSize mediaSize)106     private void addPrinter(StubbablePrinterDiscoverySession session,
107             String name, PrintAttributes.MediaSize mediaSize) {
108         PrinterId printerId = session.getService().generatePrinterId(name);
109         List<PrinterInfo> printers = new ArrayList<>(1);
110 
111         PrinterCapabilitiesInfo.Builder builder =
112                 new PrinterCapabilitiesInfo.Builder(printerId);
113 
114         PrinterInfo printerInfo;
115         if (mediaSize != null) {
116             builder.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0))
117                     .setColorModes(PrintAttributes.COLOR_MODE_COLOR,
118                             PrintAttributes.COLOR_MODE_COLOR)
119                     .addMediaSize(mediaSize, true)
120                     .addResolution(new PrintAttributes.Resolution("300x300", "300x300", 300, 300),
121                             true);
122 
123             printerInfo = new PrinterInfo.Builder(printerId, name,
124                     PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build();
125         } else {
126             printerInfo = (new PrinterInfo.Builder(printerId, name,
127                     PrinterInfo.STATUS_IDLE)).build();
128         }
129 
130         printers.add(printerInfo);
131         session.addPrinters(printers);
132     }
133 
134     /** Find a certain element in the UI and click on it */
clickOn(UiSelector selector)135     private void clickOn(UiSelector selector) throws UiObjectNotFoundException {
136         Log.i(LOG_TAG, "Click on " + selector);
137         UiObject view = getUiDevice().findObject(selector);
138         view.click();
139         getUiDevice().waitForIdle();
140     }
141 
142     /** Find a certain text in the UI and click on it */
clickOnText(String text)143     private void clickOnText(String text) throws UiObjectNotFoundException {
144         clickOn(new UiSelector().text(text));
145     }
146 
147     /** Set the printer in the print activity */
setPrinter(String printerName)148     private void setPrinter(String printerName) throws UiObjectNotFoundException {
149         clickOn(new UiSelector().resourceId("com.android.printspooler:id/destination_spinner"));
150 
151         clickOnText(printerName);
152     }
153 
154     /**
155      * Init mock print servic that returns a single printer by default.
156      *
157      * @param sessionRef Where to store the reference to the session once started
158      */
setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef, ArrayList<String> trackedPrinters, PrintAttributes.MediaSize mediaSize)159     private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef,
160             ArrayList<String> trackedPrinters, PrintAttributes.MediaSize mediaSize) {
161         FirstPrintService.setCallbacks(createMockPrintServiceCallbacks(
162                 inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> {
163                             synchronized (sessionRef) {
164                                 sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock())
165                                         .getSession();
166 
167                                 addPrinter(sessionRef[0], "1st printer", mediaSize);
168 
169                                 sessionRef.notifyAll();
170                             }
171                             return null;
172                         },
173                         null, null, inv2 -> {
174                             synchronized (trackedPrinters) {
175                                 trackedPrinters
176                                         .add(((PrinterId) inv2.getArguments()[0]).getLocalId());
177                                 trackedPrinters.notifyAll();
178                             }
179                             return null;
180                         }, null, inv2 -> {
181                             synchronized (trackedPrinters) {
182                                 trackedPrinters
183                                         .remove(((PrinterId) inv2.getArguments()[0]).getLocalId());
184                                 trackedPrinters.notifyAll();
185                             }
186                             return null;
187                         }, inv2 -> {
188                             synchronized (sessionRef) {
189                                 sessionRef[0] = null;
190                                 sessionRef.notifyAll();
191                             }
192                             return null;
193                         }
194                 ), null, null));
195     }
196 
197     /**
198      * Start print operation that just prints a single empty page
199      *
200      * @param printAttributesRef Where to store the reference to the print attributes once started
201      */
print(PrintAttributes[] printAttributesRef)202     private void print(PrintAttributes[] printAttributesRef) {
203         print(new PrintDocumentAdapter() {
204             @Override
205             public void onStart() {
206             }
207 
208             @Override
209             public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes,
210                     CancellationSignal cancellationSignal, LayoutResultCallback callback,
211                     Bundle extras) {
212                 callback.onLayoutFinished((new PrintDocumentInfo.Builder("doc")).build(),
213                         !newAttributes.equals(printAttributesRef[0]));
214 
215                 synchronized (printAttributesRef) {
216                     printAttributesRef[0] = newAttributes;
217                     printAttributesRef.notifyAll();
218                 }
219             }
220 
221             @Override
222             public void onWrite(PageRange[] pages, ParcelFileDescriptor destination,
223                     CancellationSignal cancellationSignal, WriteResultCallback callback) {
224                 try {
225                     try {
226                         PrintedPdfDocument document = new PrintedPdfDocument(getActivity(),
227                                 printAttributesRef[0]);
228                         try {
229                             PdfDocument.Page page = document.startPage(0);
230                             document.finishPage(page);
231                             try (FileOutputStream os = new FileOutputStream(
232                                     destination.getFileDescriptor())) {
233                                 document.writeTo(os);
234                                 os.flush();
235                             }
236                         } finally {
237                             document.close();
238                         }
239                     } finally {
240                         destination.close();
241                     }
242 
243                     callback.onWriteFinished(pages);
244                 } catch (IOException e) {
245                     callback.onWriteFailed(e.getMessage());
246                 }
247             }
248         }, (PrintAttributes) null);
249     }
250 
251     @Parameterized.Parameters
getParameters()252     public static Collection<Object[]> getParameters() {
253         ArrayList<Object[]> tests = new ArrayList<>();
254 
255         for (PrintAttributes.MediaSize first : new PrintAttributes.MediaSize[]{
256                 PrintAttributes.MediaSize.ISO_A0, null}) {
257             for (Boolean selectPrinter : new Boolean[]{true, false}) {
258                 for (PrintAttributes.MediaSize second : new PrintAttributes.MediaSize[]{
259                         PrintAttributes.MediaSize.ISO_A1, null}) {
260                     // If we do not use the second printer, no need to try various options
261                     if (!selectPrinter && second == null) {
262                         continue;
263                     }
264                     tests.add(new Object[]{first, selectPrinter, second});
265                 }
266             }
267         }
268 
269         return tests;
270     }
271 
272     @Test
273     @LargeTest
addAndSelectPrinter()274     public void addAndSelectPrinter() throws Exception {
275         final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1];
276         final PrintAttributes printAttributes[] = new PrintAttributes[1];
277         ArrayList<String> trackedPrinters = new ArrayList<>();
278 
279         Log.i(LOG_TAG, "Running " + mFirst + " " + mSelectPrinter + " " + mSecond);
280 
281         setMockPrintServiceCallbacks(session, trackedPrinters, mFirst);
282         print(printAttributes);
283 
284         // We are now in the PrintActivity
285         Log.i(LOG_TAG, "Waiting for session");
286         synchronized (session) {
287             waitWithTimeout(() -> session[0] == null, session::wait);
288         }
289 
290         setPrinter("1st printer");
291 
292         Log.i(LOG_TAG, "Waiting for 1st printer to be tracked");
293         synchronized (trackedPrinters) {
294             waitWithTimeout(() -> !trackedPrinters.contains("1st printer"), trackedPrinters::wait);
295         }
296 
297         if (mFirst != null) {
298             Log.i(LOG_TAG, "Waiting for print attributes to change");
299             synchronized (printAttributes) {
300                 waitWithTimeout(
301                         () -> printAttributes[0] == null ||
302                                 !printAttributes[0].getMediaSize().equals(
303                                         mFirst), printAttributes::wait);
304             }
305         } else {
306             Log.i(LOG_TAG, "Waiting for error message");
307             assertNotNull(getUiDevice().wait(Until.findObject(
308                     By.text("This printer isn't available right now.")), OPERATION_TIMEOUT_MILLIS));
309         }
310 
311         setPrinter("All printers\u2026");
312 
313         // We are now in the SelectPrinterActivity
314         clickOnText("Add printer");
315 
316         // We are now in the AddPrinterActivity
317         AddPrintersActivity.addObserver(
318                 () -> addPrinter(session[0], "2nd printer", mSecond));
319 
320         // This executes the observer registered above
321         clickOn(new UiSelector().text(FirstPrintService.class.getCanonicalName())
322                 .resourceId("com.android.printspooler:id/title"));
323 
324         getUiDevice().pressBack();
325         AddPrintersActivity.clearObservers();
326 
327         if (mSelectPrinter) {
328             // We are now in the SelectPrinterActivity
329             clickOnText("2nd printer");
330         } else {
331             getUiDevice().pressBack();
332         }
333 
334         // We are now in the PrintActivity
335         if (mSelectPrinter) {
336             if (mSecond != null) {
337                 Log.i(LOG_TAG, "Waiting for print attributes to change");
338                 synchronized (printAttributes) {
339                     waitWithTimeout(
340                             () -> printAttributes[0] == null ||
341                                     !printAttributes[0].getMediaSize().equals(
342                                             mSecond), printAttributes::wait);
343                 }
344             } else {
345                 Log.i(LOG_TAG, "Waiting for error message");
346                 assertNotNull(getUiDevice().wait(Until.findObject(
347                         By.text("This printer isn't available right now.")),
348                         OPERATION_TIMEOUT_MILLIS));
349             }
350 
351             Log.i(LOG_TAG, "Waiting for 1st printer to be not tracked");
352             synchronized (trackedPrinters) {
353                 waitWithTimeout(() -> trackedPrinters.contains("1st printer"),
354                         trackedPrinters::wait);
355             }
356 
357             Log.i(LOG_TAG, "Waiting for 2nd printer to be tracked");
358             synchronized (trackedPrinters) {
359                 waitWithTimeout(() -> !trackedPrinters.contains("2nd printer"),
360                         trackedPrinters::wait);
361             }
362         } else {
363             Thread.sleep(100);
364 
365             if (mFirst != null) {
366                 Log.i(LOG_TAG, "Waiting for print attributes to change");
367                 synchronized (printAttributes) {
368                     waitWithTimeout(
369                             () -> printAttributes[0] == null ||
370                                     !printAttributes[0].getMediaSize().equals(
371                                             mFirst), printAttributes::wait);
372                 }
373             } else {
374                 Log.i(LOG_TAG, "Waiting for error message");
375                 assertNotNull(getUiDevice().wait(Until.findObject(
376                         By.text("This printer isn't available right now.")),
377                         OPERATION_TIMEOUT_MILLIS));
378             }
379 
380             Log.i(LOG_TAG, "Waiting for 1st printer to be tracked");
381             synchronized (trackedPrinters) {
382                 waitWithTimeout(() -> !trackedPrinters.contains("1st printer"),
383                         trackedPrinters::wait);
384             }
385         }
386 
387         getUiDevice().pressBack();
388 
389         // We are back in the test activity
390         Log.i(LOG_TAG, "Waiting for session to end");
391         synchronized (session) {
392             waitWithTimeout(() -> session[0] != null, session::wait);
393         }
394     }
395 }
396