blob: 3feb0e905f1a870a393cc742caf91a87422a1818 [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.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.content.res.Configuration;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
import android.print.PrintAttributes;
import android.print.PrintDocumentAdapter;
import android.print.PrintManager;
import android.print.PrinterId;
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.test.InstrumentationTestCase;
import android.util.DisplayMetrics;
import android.util.LocaleList;
import org.mockito.stubbing.Answer;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.TimeoutException;
/**
* This is the base class for print tests.
*/
public abstract class BasePrintTest extends InstrumentationTestCase {
private static final long OPERATION_TIMEOUT = 30000;
private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler";
private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
private static final int CURRENT_USER_ID = -2; // Mirrors UserHandle.USER_CURRENT
private PrintTestActivity mActivity;
private android.print.PrintJob mPrintJob;
private LocaleList mOldLocale;
private CallCounter mStartCallCounter;
private CallCounter mStartSessionCallCounter;
private String[] mEnabledImes;
private String[] getEnabledImes() throws IOException {
List<String> imeList = new ArrayList<>();
ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
.executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())))) {
String line;
while ((line = reader.readLine()) != null) {
imeList.add(line);
}
}
String[] imeArray = new String[imeList.size()];
imeList.toArray(imeArray);
return imeArray;
}
private void disableImes() throws Exception {
mEnabledImes = getEnabledImes();
for (String ime : mEnabledImes) {
String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
runShellCommand(getInstrumentation(), disableImeCommand);
}
}
private void enableImes() throws Exception {
for (String ime : mEnabledImes) {
String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
runShellCommand(getInstrumentation(), enableImeCommand);
}
mEnabledImes = null;
}
@Override
protected void runTest() throws Throwable {
// Do nothing if the device does not support printing.
if (supportsPrinting()) {
super.runTest();
}
}
@Override
public void setUp() throws Exception {
super.setUp();
if (!supportsPrinting()) {
return;
}
// Make sure we start with a clean slate.
clearPrintSpoolerData();
disableImes();
// 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());
// Set to US locale.
Resources resources = getInstrumentation().getTargetContext().getResources();
Configuration oldConfiguration = resources.getConfiguration();
if (!oldConfiguration.getLocales().get(0).equals(Locale.US)) {
mOldLocale = oldConfiguration.getLocales();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
Configuration newConfiguration = new Configuration(oldConfiguration);
newConfiguration.setLocale(Locale.US);
resources.updateConfiguration(newConfiguration, displayMetrics);
}
// Initialize the latches.
mStartCallCounter = new CallCounter();
mStartSessionCallCounter = new CallCounter();
// Create the activity for the right locale.
createActivity();
}
@Override
public void tearDown() throws Exception {
if (!supportsPrinting()) {
return;
}
// Done with the activity.
getActivity().finish();
enableImes();
// Restore the locale if needed.
if (mOldLocale != null) {
Resources resources = getInstrumentation().getTargetContext().getResources();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
Configuration newConfiguration = new Configuration(resources.getConfiguration());
newConfiguration.setLocales(mOldLocale);
mOldLocale = null;
resources.updateConfiguration(newConfiguration, displayMetrics);
}
// Make sure the spooler is cleaned, this also un-approves all services
clearPrintSpoolerData();
super.tearDown();
}
protected android.print.PrintJob print(@NonNull final PrintDocumentAdapter adapter,
final PrintAttributes attributes) {
// Initiate printing as if coming from the app.
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
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 void waitForCallbackCallCount(CallCounter counter, int count, String message) {
try {
counter.waitForCount(count, OPERATION_TIMEOUT);
} catch (TimeoutException te) {
fail(message);
}
}
protected PrintTestActivity getActivity() {
return mActivity;
}
protected void createActivity() {
mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
PrintTestActivity.class, null);
}
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);
StringBuffer stdout = new StringBuffer();
while ((bytesRead = fis.read(buf)) != -1) {
stdout.append(new String(buf, 0, bytesRead));
}
fis.close();
return stdout.toString();
}
protected void clearPrintSpoolerData() throws Exception {
assertTrue("failed to clear print spooler data",
runShellCommand(getInstrumentation(), String.format(
"pm clear --user %d %s", CURRENT_USER_ID, 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(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;
}
protected final class CallCounter {
private final Object mLock = new Object();
private int mCallCount;
public void call() {
synchronized (mLock) {
mCallCount++;
mLock.notifyAll();
}
}
public 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 */
}
}
}
}
}
protected boolean supportsPrinting() {
return getInstrumentation().getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_PRINTING);
}
}