blob: 8ef806290230d437407d1c70b8cb8775f005fbf5 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.print;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.print.mockservice.PrintServiceCallbacks;
import android.print.mockservice.PrinterDiscoverySessionCallbacks;
import android.print.mockservice.StubbablePrinterDiscoverySession;
import android.printservice.CustomPrinterIconCallback;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
import android.support.test.rule.ActivityTestRule;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.mockito.stubbing.Answer;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeoutException;
/**
* This is the base class for print tests.
*/
abstract class BasePrintTest {
protected static final long OPERATION_TIMEOUT = 30000;
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
private android.print.PrintJob mPrintJob;
private CallCounter mStartCallCounter;
private CallCounter mStartSessionCallCounter;
private static Instrumentation sInstrumentation;
private static UiDevice sUiDevice;
@Rule
public ActivityTestRule<PrintTestActivity> mActivityRule =
new ActivityTestRule<>(PrintTestActivity.class, false, true);
/**
* {@link Runnable} that can throw and {@link Exception}
*/
interface Invokable {
/**
* Execute the invokable
*
* @throws Exception
*/
void run() throws Exception;
}
/**
* Assert that the invokable throws an expectedException
*
* @param invokable The {@link Invokable} to run
* @param expectedClass The {@link Exception} that is supposed to be thrown
*/
void assertException(Invokable invokable, Class<? extends Exception> expectedClass)
throws Exception {
try {
invokable.run();
} catch (Exception e) {
if (e.getClass().isAssignableFrom(expectedClass)) {
return;
} else {
throw e;
}
}
throw new AssertionError("No exception thrown");
}
/**
* Return the UI device
*
* @return the UI device
*/
public UiDevice getUiDevice() {
return sUiDevice;
}
protected static Instrumentation getInstrumentation() {
return sInstrumentation;
}
@BeforeClass
public static void setUpClass() throws Exception {
sInstrumentation = InstrumentationRegistry.getInstrumentation();
assumeTrue(sInstrumentation.getContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_PRINTING));
sUiDevice = UiDevice.getInstance(sInstrumentation);
// Make sure we start with a clean slate.
clearPrintSpoolerData();
// Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
// Dexmaker is used by mockito.
System.setProperty("dexmaker.dexcache", getInstrumentation()
.getTargetContext().getCacheDir().getPath());
}
@Before
public void initCounters() throws Exception {
// Initialize the latches.
mStartCallCounter = new CallCounter();
mStartSessionCallCounter = new CallCounter();
}
@After
public void exitActivities() throws Exception {
// Exit print spooler
getUiDevice().pressBack();
getUiDevice().pressBack();
}
protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
final PrintAttributes attributes) {
// Initiate printing as if coming from the app.
getInstrumentation().runOnMainSync(() -> {
PrintManager printManager = (PrintManager) getActivity()
.getSystemService(Context.PRINT_SERVICE);
mPrintJob = printManager.print("Print job", adapter, attributes);
});
return mPrintJob;
}
protected void onStartCalled() {
mStartCallCounter.call();
}
protected void onPrinterDiscoverySessionStartCalled() {
mStartSessionCallCounter.call();
}
protected void waitForPrinterDiscoverySessionStartCallbackCalled() {
waitForCallbackCallCount(mStartSessionCallCounter, 1,
"Did not get expected call to onStartPrinterDiscoverySession.");
}
protected void waitForStartAdapterCallbackCalled() {
waitForCallbackCallCount(mStartCallCounter, 1, "Did not get expected call to start.");
}
private static void waitForCallbackCallCount(CallCounter counter, int count, String message) {
try {
counter.waitForCount(count, OPERATION_TIMEOUT);
} catch (TimeoutException te) {
fail(message);
}
}
protected PrintTestActivity getActivity() {
return mActivityRule.getActivity();
}
public static String runShellCommand(Instrumentation instrumentation, String cmd)
throws IOException {
ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
byte[] buf = new byte[512];
int bytesRead;
FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
StringBuilder stdout = new StringBuilder();
while ((bytesRead = fis.read(buf)) != -1) {
stdout.append(new String(buf, 0, bytesRead));
}
fis.close();
return stdout.toString();
}
protected static void clearPrintSpoolerData() throws Exception {
assertTrue("failed to clear print spooler data",
runShellCommand(getInstrumentation(), String.format(
"pm clear --user %d %s", CURRENT_USER_ID,
PrintManager.PRINT_SPOOLER_PACKAGE_NAME))
.contains(PM_CLEAR_SUCCESS_OUTPUT));
}
@SuppressWarnings("unchecked")
protected PrinterDiscoverySessionCallbacks createMockPrinterDiscoverySessionCallbacks(
Answer<Void> onStartPrinterDiscovery, Answer<Void> onStopPrinterDiscovery,
Answer<Void> onValidatePrinters, Answer<Void> onStartPrinterStateTracking,
Answer<Void> onRequestCustomPrinterIcon, Answer<Void> onStopPrinterStateTracking,
Answer<Void> onDestroy) {
PrinterDiscoverySessionCallbacks callbacks = mock(PrinterDiscoverySessionCallbacks.class);
doCallRealMethod().when(callbacks).setSession(any(StubbablePrinterDiscoverySession.class));
when(callbacks.getSession()).thenCallRealMethod();
if (onStartPrinterDiscovery != null) {
doAnswer(onStartPrinterDiscovery).when(callbacks).onStartPrinterDiscovery(
any(List.class));
}
if (onStopPrinterDiscovery != null) {
doAnswer(onStopPrinterDiscovery).when(callbacks).onStopPrinterDiscovery();
}
if (onValidatePrinters != null) {
doAnswer(onValidatePrinters).when(callbacks).onValidatePrinters(
any(List.class));
}
if (onStartPrinterStateTracking != null) {
doAnswer(onStartPrinterStateTracking).when(callbacks).onStartPrinterStateTracking(
any(PrinterId.class));
}
if (onRequestCustomPrinterIcon != null) {
doAnswer(onRequestCustomPrinterIcon).when(callbacks).onRequestCustomPrinterIcon(
any(PrinterId.class), any(CancellationSignal.class),
any(CustomPrinterIconCallback.class));
}
if (onStopPrinterStateTracking != null) {
doAnswer(onStopPrinterStateTracking).when(callbacks).onStopPrinterStateTracking(
any(PrinterId.class));
}
if (onDestroy != null) {
doAnswer(onDestroy).when(callbacks).onDestroy();
}
return callbacks;
}
protected PrintServiceCallbacks createMockPrintServiceCallbacks(
Answer<PrinterDiscoverySessionCallbacks> onCreatePrinterDiscoverySessionCallbacks,
Answer<Void> onPrintJobQueued, Answer<Void> onRequestCancelPrintJob) {
final PrintServiceCallbacks service = mock(PrintServiceCallbacks.class);
doCallRealMethod().when(service).setService(any(PrintService.class));
when(service.getService()).thenCallRealMethod();
if (onCreatePrinterDiscoverySessionCallbacks != null) {
doAnswer(onCreatePrinterDiscoverySessionCallbacks).when(service)
.onCreatePrinterDiscoverySessionCallbacks();
}
if (onPrintJobQueued != null) {
doAnswer(onPrintJobQueued).when(service).onPrintJobQueued(any(PrintJob.class));
}
if (onRequestCancelPrintJob != null) {
doAnswer(onRequestCancelPrintJob).when(service).onRequestCancelPrintJob(
any(PrintJob.class));
}
return service;
}
private static final class CallCounter {
private final Object mLock = new Object();
private int mCallCount;
public void call() {
synchronized (mLock) {
mCallCount++;
mLock.notifyAll();
}
}
int getCallCount() {
synchronized (mLock) {
return mCallCount;
}
}
public void waitForCount(int count, long timeoutMillis) throws TimeoutException {
synchronized (mLock) {
final long startTimeMillis = SystemClock.uptimeMillis();
while (mCallCount < count) {
try {
final long elapsedTimeMillis = SystemClock.uptimeMillis() - startTimeMillis;
final long remainingTimeMillis = timeoutMillis - elapsedTimeMillis;
if (remainingTimeMillis <= 0) {
throw new TimeoutException();
}
mLock.wait(timeoutMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
}
}
}
}