blob: 23ae620a3fdb156d0dc26dc9f91926ee050e8421 [file] [log] [blame]
/*
* Copyright (C) 2014 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.cts;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.print.PageRange;
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentAdapter.LayoutResultCallback;
import android.print.PrintDocumentAdapter.WriteResultCallback;
import android.print.PrintDocumentInfo;
import android.print.PrinterCapabilitiesInfo;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.print.test.BasePrintTest;
import android.print.test.services.FirstPrintService;
import android.print.test.services.PrintServiceCallbacks;
import android.print.test.services.PrinterDiscoverySessionCallbacks;
import android.print.test.services.SecondPrintService;
import android.print.test.services.StubbablePrinterDiscoverySession;
import android.printservice.PrintJob;
import android.printservice.PrintService;
import android.util.Log;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This test verifies that the system respects the {@link PrintDocumentAdapter}
* contract and invokes all callbacks as expected.
*/
@RunWith(AndroidJUnit4.class)
public class PrintDocumentAdapterContractTest extends BasePrintTest {
private static final String LOG_TAG = "PrintDocumentAdapterContractTest";
@Before
public void setDefaultPrinter() throws Exception {
FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
}
@Before
public void clearPrintSpoolerState() throws Exception {
clearPrintSpoolerData();
}
@Test
public void noPrintOptionsOrPrinterChange() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(2)
.build();
callback.onLayoutFinished(info, false);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(new PageRange[]{new PageRange(0, 1)});
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 1)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// We selected the second printer which does not support the media
// size that was selected, so a new layout happens as the size changed.
// Since we passed false to the layout callback meaning that the content
// didn't change, there shouldn't be a next call to write.
PrintAttributes secondOldAttributes = firstNewAttributes;
PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
// When print is pressed we ask for all selected pages but we got
// them when asking for the ones for a preview, and the adapter does
// not report a content change. Hence, there is nothing to write.
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void noPrintOptionsOrPrinterChangeCanceled() throws Exception {
// Create a mock print adapter.
PrintDocumentAdapter adapter = createDefaultPrintDocumentAdapter(1);
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Cancel the printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void nonCallingBackWrite() throws Throwable {
final PrintAttributes[] lastLayoutPrintAttributes = new PrintAttributes[1];
final boolean[] isWriteBroken = new boolean[1];
// Create a mock print adapter.
PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
PrintAttributes printAttributes =
(PrintAttributes) invocation.getArguments()[1];
PrintDocumentAdapter.LayoutResultCallback callback =
(PrintDocumentAdapter.LayoutResultCallback) invocation
.getArguments()[3];
callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN).build(),
lastLayoutPrintAttributes[0] != printAttributes);
lastLayoutPrintAttributes[0] = printAttributes;
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PrintDocumentAdapter.WriteResultCallback callback =
(PrintDocumentAdapter.WriteResultCallback) args[3];
if (isWriteBroken[0]) {
((CancellationSignal) args[2])
.setOnCancelListener(() -> callback.onWriteCancelled());
} else {
try (ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1]) {
writeBlankPages(lastLayoutPrintAttributes[0], fd, 0, 1);
}
callback.onWriteFinished(new PageRange[]{new PageRange(0, 0)});
}
onWriteCalled();
return null;
}, invocation -> {
onFinishCalled();
return null;
});
// never return from writes until we repair the write call later
isWriteBroken[0] = false;
// Start printing.
print(adapter);
// Wait for write. This will happen as the the first layout always triggers a write
waitForWriteAdapterCallback(1);
// Make write broken
isWriteBroken[0] = true;
selectPrinter("Second printer");
assertNoPrintButton();
// Wait for write (The second printer causes a re-layout as it does not support the default
// page size). The write is broken, hence this never returns
waitForWriteAdapterCallback(2);
assertNoPrintButton();
getUiDevice().pressBack();
getUiDevice().pressBack();
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
@Test
public void nonChangePrinterWhileNotWritten() throws Exception {
final PrintAttributes[] lastLayoutPrintAttributes = new PrintAttributes[1];
final boolean[] isWriteBroken = new boolean[1];
// Create a mock print adapter.
PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
PrintAttributes printAttributes =
(PrintAttributes) invocation.getArguments()[1];
PrintDocumentAdapter.LayoutResultCallback callback =
(PrintDocumentAdapter.LayoutResultCallback) invocation
.getArguments()[3];
callback.onLayoutFinished(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setPageCount(PrintDocumentInfo.PAGE_COUNT_UNKNOWN).build(),
lastLayoutPrintAttributes[0] != printAttributes);
lastLayoutPrintAttributes[0] = printAttributes;
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PrintDocumentAdapter.WriteResultCallback callback =
(PrintDocumentAdapter.WriteResultCallback) args[3];
if (isWriteBroken[0]) {
((CancellationSignal) args[2]).setOnCancelListener(() -> {
callback.onWriteCancelled();
onWriteCancelCalled();
});
} else {
try (ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1]) {
writeBlankPages(lastLayoutPrintAttributes[0], fd, 0, 1);
}
callback.onWriteFinished(new PageRange[]{new PageRange(0, 1)});
}
onWriteCalled();
return null;
}, invocation -> {
onFinishCalled();
return null;
});
// Fake a very long write
isWriteBroken[0] = true;
// Start printing.
print(adapter);
// Wait for write. This will happen as the the first layout always triggers a write
waitForWriteAdapterCallback(1);
// Repair write and cause a re-layout by changing the printer
isWriteBroken[0] = false;
selectPrinter("Second printer");
// The first write should be canceled and the second write succeeds
waitForWriteCancelCallback(1);
waitForWriteAdapterCallback(2);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
@Test
public void printOptionsChangeAndNoPrinterChange() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback)
invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(pages);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Open the print options.
openPrintOptions();
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Change the orientation.
changeOrientation("Landscape");
// Wait for layout.
waitForLayoutAdapterCallbackCount(3);
// Change the media size.
changeMediaSize(MediaSize.ISO_A0.getLabel(getActivity().getPackageManager()));
// Wait for layout.
waitForLayoutAdapterCallbackCount(4);
// Change the color.
changeColor("Black & White");
// Wait for layout.
waitForLayoutAdapterCallbackCount(5);
// Change the duplex.
changeDuplex("Short edge");
// Wait for layout.
waitForLayoutAdapterCallbackCount(6);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// We selected the second printer which does not support the media
// size that was selected, so a new layout happens as the size changed.
// Since we passed false to the layout callback meaning that the content
// didn't change, there shouldn't be a next call to write.
PrintAttributes secondOldAttributes = firstNewAttributes;
PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
// We changed the orientation which triggers a layout. Since we passed
// false to the layout callback meaning that the content didn't change,
// there shouldn't be a next call to write.
PrintAttributes thirdOldAttributes = secondNewAttributes;
PrintAttributes thirdNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3.asLandscape())
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, thirdOldAttributes, thirdNewAttributes, true);
// We changed the media size which triggers a layout. Since we passed
// false to the layout callback meaning that the content didn't change,
// there shouldn't be a next call to write.
PrintAttributes fourthOldAttributes = thirdNewAttributes;
PrintAttributes fourthNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A0.asLandscape())
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, fourthOldAttributes, fourthNewAttributes, true);
// We changed the color which triggers a layout. Since we passed
// false to the layout callback meaning that the content didn't change,
// there shouldn't be a next call to write.
PrintAttributes fifthOldAttributes = fourthNewAttributes;
PrintAttributes fifthNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A0.asLandscape())
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, fifthOldAttributes, fifthNewAttributes, true);
// We changed the duplex which triggers a layout. Since we passed
// false to the layout callback meaning that the content didn't change,
// there shouldn't be a next call to write.
PrintAttributes sixthOldAttributes = fifthNewAttributes;
PrintAttributes sixthNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A0.asLandscape())
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_SHORT_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, sixthOldAttributes, sixthNewAttributes, true);
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, sixthNewAttributes, sixthNewAttributes, false);
// When print is pressed we ask for all selected pages but we got
// them when asking for the ones for a preview, and the adapter does
// not report a content change. Hence, there is nothing to write.
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
/* Disabled @Test, as this will intentionally kill the activity that started the test */
public void printCorruptedFile() throws Exception {
final boolean[] writeCorruptedFile = new boolean[1];
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback)
invocation.getArguments()[3];
Bundle extras = (Bundle) invocation.getArguments()[4];
Log.i(LOG_TAG, "Preview: " + extras.getBoolean(
PrintDocumentAdapter.EXTRA_PRINT_PREVIEW));
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.build();
callback.onLayoutFinished(info, true);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
if (writeCorruptedFile[0]) {
Log.i(LOG_TAG, "write corrupted file " + Arrays.toString(pages));
FileOutputStream os = new FileOutputStream(fd.getFileDescriptor());
for (int i = 0; i < 10; i++) {
os.write(i);
}
} else {
Log.i(LOG_TAG, "write good file");
writeBlankPages(printAttributes[0], fd, 0, 1);
}
fd.close();
callback.onWriteFinished(new PageRange[]{new PageRange(0, 1)});
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Select the second printer.
selectPrinter("First printer");
openPrintOptions();
selectPages("1", 2);
// Wait for write.
waitForWriteAdapterCallback(2);
writeCorruptedFile[0] = true;
// Click the print button
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Printing will abort automatically
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
@Test
public void printOptionsChangeAndPrinterChange() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback)
invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(pages);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Open the print options.
openPrintOptions();
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Change the color.
changeColor("Black & White");
// Wait for layout.
waitForLayoutAdapterCallbackCount(3);
// Change the printer to one which supports the current media size.
// Select the second printer.
selectPrinter("First printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(4);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// We changed the printer and the new printer does not support the
// selected media size in which case the default media size of the
// printer is used resulting in a layout pass. Same for margins.
PrintAttributes secondOldAttributes = firstNewAttributes;
PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(new Margins(0, 0, 0, 0))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
// We changed the color which results in a layout pass.
PrintAttributes thirdOldAttributes = secondNewAttributes;
PrintAttributes thirdNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(new Margins(0, 0, 0, 0))
.setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, thirdOldAttributes, thirdNewAttributes, true);
// We changed the printer to one that does not support the current
// media size in which case we pick the default media size for the
// new printer which results in a layout pass. Same for color.
PrintAttributes fourthOldAttributes = thirdNewAttributes;
PrintAttributes fourthNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A0)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(new Margins(200, 200, 200, 200))
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, fourthOldAttributes, fourthNewAttributes, true);
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, fourthNewAttributes, fourthNewAttributes, false);
// When print is pressed we ask for all selected pages but we got
// them when asking for the ones for a preview, and the adapter does
// not report a content change. Hence, there is nothing to write.
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void printOptionsChangeAndNoPrinterChangeAndContentChange()
throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
// The content changes after every layout.
callback.onLayoutFinished(info, true);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(pages);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Open the print options.
openPrintOptions();
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// We selected the second printer which does not support the media
// size that was selected, so a new layout happens as the size changed.
PrintAttributes secondOldAttributes = firstNewAttributes;
PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
// In the layout callback we reported that the content changed,
// so the previously written page has to be written again.
PageRange[] secondPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(secondPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
// When print is pressed we ask for all selected pages as the adapter
// reports that the content changes after every layout pass.
PageRange[] thirdPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(thirdPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void newPrinterSupportsSelectedPrintOptions() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
// The content changes after every layout.
callback.onLayoutFinished(info, true);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(pages);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Open the print options.
openPrintOptions();
// Select the third printer.
selectPrinter("Third printer");
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, firstNewAttributes, firstNewAttributes, false);
// When print is pressed we ask for all selected pages.
PageRange[] thirdPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(thirdPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void nothingChangesAllPagesWrittenFirstTime() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(3)
.build();
callback.onLayoutFinished(info, false);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
PageRange[] pages = (PageRange[]) args[0];
writeBlankPages(printAttributes[0], fd, pages[0].getStart(), pages[0].getEnd());
fd.close();
callback.onWriteFinished(new PageRange[] {PageRange.ALL_PAGES});
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Open the print options.
openPrintOptions();
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the the first fifty pages for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 2)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// We selected the second printer which does not support the media
// size that was selected, so a new layout happens as the size changed.
PrintAttributes secondOldAttributes = firstNewAttributes;
PrintAttributes secondNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.ISO_A3)
.setResolution(new Resolution("300x300", "300x300", 300, 300))
.setMinMargins(Margins.NO_MARGINS)
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_LONG_EDGE)
.build();
verifyLayoutCall(inOrder, adapter, secondOldAttributes, secondNewAttributes, true);
// In the layout callback we reported that the content didn't change,
// and we wrote all pages in the write call while being asked only
// for the first page. Hence, all pages were written and they didn't
// change, therefore no subsequent write call should happen.
// When print is pressed we ask for a layout which is *not* for preview.
verifyLayoutCall(inOrder, adapter, secondNewAttributes, secondNewAttributes, false);
// In the layout callback we reported that the content didn't change,
// and we wrote all pages in the write call while being asked only
// for the first page. Hence, all pages were written and they didn't
// change, therefore no subsequent write call should happen.
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void cancelLongRunningLayout() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
CancellationSignal cancellation = (CancellationSignal) invocation.getArguments()[2];
final LayoutResultCallback callback = (LayoutResultCallback) invocation
.getArguments()[3];
cancellation.setOnCancelListener(() -> {
onCancelOperationCalled();
callback.onLayoutCancelled();
});
onLayoutCalled();
return null;
}, null, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for the cancellation request.
waitForCancelOperationCallbackCalled();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void cancelLongRunningWrite() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
final ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
final CancellationSignal cancellation = (CancellationSignal) args[2];
final WriteResultCallback callback = (WriteResultCallback) args[3];
cancellation.setOnCancelListener(() -> {
try {
fd.close();
} catch (IOException ioe) {
/* ignore */
}
onCancelOperationCalled();
callback.onWriteCancelled();
});
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for the cancellation request.
waitForCancelOperationCallbackCalled();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the first page for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void failedLayout() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
callback.onLayoutFailed(null);
// Mark layout was called.
onLayoutCalled();
return null;
}, null, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// No write as layout failed.
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void failedWrite() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
fd.close();
callback.onWriteFailed(null);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the first page for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void unexpectedLayoutCancel() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
final int[] numLayoutCalls = new int[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
// Returned cancelled for the second layout call which is unexpected
if (numLayoutCalls[0] == 1) {
callback.onLayoutCancelled();
} else {
callback.onLayoutFinished(
(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)).build(), false);
}
numLayoutCalls[0]++;
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, 0, 1);
fd.close();
callback.onWriteFinished(pages);
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForLayoutAdapterCallbackCount(2);
// Retry layout (which should succeed) as the layout call will stop canceling after the
// second time
clickRetryButton();
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
@Test
public void unexpectedWriteCancel() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
final int[] numWriteCalls = new int[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
callback.onLayoutFinished(
(new PrintDocumentInfo.Builder(PRINT_JOB_NAME)).build(), true);
// Mark layout was called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
WriteResultCallback callback = (WriteResultCallback) args[3];
// Returned cancelled for the second write call which is unexpected
if (numWriteCalls[0] == 1) {
callback.onWriteCancelled();
} else {
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
writeBlankPages(printAttributes[0], fd, 0, 1);
fd.close();
callback.onWriteFinished(pages);
}
numWriteCalls[0]++;
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
waitForWriteAdapterCallback(1);
// Select the second printer.
selectPrinter("Second printer");
// Wait for layout.
waitForWriteAdapterCallback(2);
// Retry write (which should succeed) as the write call will stop canceling after the
// second time
clickRetryButton();
// Click the print button.
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
@Test
public void requestedPagesNotWritten() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
writeBlankPages(printAttributes[0], fd, Integer.MAX_VALUE, Integer.MAX_VALUE);
fd.close();
// Write wrong pages.
callback.onWriteFinished(new PageRange[] {
new PageRange(Integer.MAX_VALUE,Integer.MAX_VALUE)});
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the first page for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void layoutCallbackNotCalled() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
// Break the contract and never call the callback.
// Mark layout called.
onLayoutCalled();
return null;
}, null, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void writeCallbackNotCalled() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
LayoutResultCallback callback = (LayoutResultCallback) invocation.getArguments()[3];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).setPageCount(1)
.build();
callback.onLayoutFinished(info, false);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
fd.close();
// Break the contract and never call the callback.
// Mark write was called.
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for write.
waitForWriteAdapterCallback(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
// We always ask for the first page for preview.
PageRange[] firstPages = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPages), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void layoutCallbackCalledTwice() throws Exception {
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
LayoutResultCallback callback = (LayoutResultCallback) invocation
.getArguments()[3];
// Break the contract and call the callback twice.
callback.onLayoutFinished(info, true);
callback.onLayoutFinished(info, true);
// Mark layout called.
onLayoutCalled();
return null;
}, null, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
PageRange[] firstPage = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPage), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
@Test
public void writeCallbackCalledTwice() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
// Create a mock print adapter.
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(1)
.build();
LayoutResultCallback callback = (LayoutResultCallback) invocation
.getArguments()[3];
callback.onLayoutFinished(info, true);
// Mark layout called.
onLayoutCalled();
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
// Write only one pages
writeBlankPages(printAttributes[0], fd, 0, 0);
fd.close();
// Break the contract and call callback twice
callback.onWriteFinished(pages);
callback.onWriteFinished(pages);
// Mark write called
onWriteCalled();
return null;
}, invocation -> {
// Mark finish was called.
onFinishCalled();
return null;
});
// Start printing.
print(adapter);
// Wait for layout.
waitForLayoutAdapterCallbackCount(1);
// Cancel printing.
getUiDevice().pressBack(); // wakes up the device.
getUiDevice().pressBack();
// Wait for a finish.
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
// Verify the expected calls.
InOrder inOrder = inOrder(adapter);
// Start is always called first.
inOrder.verify(adapter).onStart();
// Start is always followed by a layout. The PDF printer is selected if
// there are other printers but none of them was used.
PrintAttributes firstOldAttributes = new PrintAttributes.Builder().build();
PrintAttributes firstNewAttributes = new PrintAttributes.Builder()
.setMediaSize(MediaSize.NA_LETTER)
.setResolution(new Resolution("PDF resolution", "PDF resolution", 300, 300))
.setMinMargins(Margins.NO_MARGINS).setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setDuplexMode(PrintAttributes.DUPLEX_MODE_NONE)
.build();
verifyLayoutCall(inOrder, adapter, firstOldAttributes, firstNewAttributes, true);
PageRange[] firstPage = new PageRange[] {new PageRange(0, 0)};
inOrder.verify(adapter).onWrite(eq(firstPage), any(ParcelFileDescriptor.class),
any(CancellationSignal.class), any(WriteResultCallback.class));
// Finish is always called last.
inOrder.verify(adapter).onFinish();
// No other call are expected.
verifyNoMoreInteractions(adapter);
}
/**
* Pretend to have written two pages, but only actually write one page
*
* @throws Exception If anything is unexpected
*/
@Test
public void notEnoughPages() throws Exception {
final PrintAttributes[] printAttributes = new PrintAttributes[1];
final PrintDocumentAdapter adapter = createMockPrintDocumentAdapter(
invocation -> {
printAttributes[0] = (PrintAttributes) invocation.getArguments()[1];
LayoutResultCallback callback = (LayoutResultCallback) invocation
.getArguments()[3];
// Lay out two pages
PrintDocumentInfo info = new PrintDocumentInfo.Builder(PRINT_JOB_NAME)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.setPageCount(2)
.build();
callback.onLayoutFinished(info, true);
return null;
}, invocation -> {
Object[] args = invocation.getArguments();
PageRange[] pages = (PageRange[]) args[0];
ParcelFileDescriptor fd = (ParcelFileDescriptor) args[1];
WriteResultCallback callback = (WriteResultCallback) args[3];
// Write only one pages
writeBlankPages(printAttributes[0], fd, 0, 0);
fd.close();
// Break the contract and report that two pages were written
callback.onWriteFinished(pages);
onWriteCalled();
return null;
}, invocation -> {
onFinishCalled();
return null;
});
print(adapter);
waitForWriteAdapterCallback(1);
selectPrinter("First printer");
clickPrintButton();
// Answer the dialog for the print service cloud warning
answerPrintServicesWarning(true);
waitForAdapterFinishCallbackCalled();
// Wait for the session to be destroyed to isolate tests.
waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
}
private PrinterDiscoverySessionCallbacks createFirstMockDiscoverySessionCallbacks() {
return createMockPrinterDiscoverySessionCallbacks(invocation -> {
PrinterDiscoverySessionCallbacks mock = (PrinterDiscoverySessionCallbacks)
invocation.getMock();
StubbablePrinterDiscoverySession session = mock.getSession();
PrintService service = session.getService();
if (session.getPrinters().isEmpty()) {
List<PrinterInfo> printers = new ArrayList<>();
// Add the first printer.
PrinterId firstPrinterId = service.generatePrinterId("first_printer");
PrinterCapabilitiesInfo firstCapabilities =
new PrinterCapabilitiesInfo.Builder(firstPrinterId)
.setMinMargins(new Margins(200, 200, 200, 200))
.addMediaSize(MediaSize.ISO_A0, true)
.addMediaSize(MediaSize.ISO_A5, false)
.addResolution(new Resolution("300x300", "300x300", 300, 300), true)
.setColorModes(PrintAttributes.COLOR_MODE_COLOR,
PrintAttributes.COLOR_MODE_COLOR)
.build();
PrinterInfo firstPrinter = new PrinterInfo.Builder(firstPrinterId,
"First printer", PrinterInfo.STATUS_IDLE)
.setCapabilities(firstCapabilities)
.build();
printers.add(firstPrinter);
// Add the second printer.
PrinterId secondPrinterId = service.generatePrinterId("second_printer");
PrinterCapabilitiesInfo secondCapabilities =
new PrinterCapabilitiesInfo.Builder(secondPrinterId)
.addMediaSize(MediaSize.ISO_A3, true)
.addMediaSize(MediaSize.ISO_A0, false)
.addResolution(new Resolution("200x200", "200x200", 200, 200), true)
.addResolution(new Resolution("300x300", "300x300", 300, 300),
false)
.setColorModes(PrintAttributes.COLOR_MODE_COLOR
| PrintAttributes.COLOR_MODE_MONOCHROME,
PrintAttributes.COLOR_MODE_MONOCHROME
)
.setDuplexModes(PrintAttributes.DUPLEX_MODE_LONG_EDGE
| PrintAttributes.DUPLEX_MODE_SHORT_EDGE,
PrintAttributes.DUPLEX_MODE_LONG_EDGE
)
.build();
PrinterInfo secondPrinter = new PrinterInfo.Builder(secondPrinterId,
"Second printer", PrinterInfo.STATUS_IDLE)
.setCapabilities(secondCapabilities)
.build();
printers.add(secondPrinter);
// Add the third printer.
PrinterId thirdPrinterId = service.generatePrinterId("third_printer");
PrinterCapabilitiesInfo thirdCapabilities =
null;
try {
thirdCapabilities = new PrinterCapabilitiesInfo.Builder(thirdPrinterId)
.addMediaSize(MediaSize.NA_LETTER, true)
.addResolution(new Resolution("300x300", "300x300", 300, 300), true)
.setColorModes(PrintAttributes.COLOR_MODE_COLOR,
PrintAttributes.COLOR_MODE_COLOR)
.build();
} catch (Exception e) {
Log.e(LOG_TAG, "Cannot create third printer", e);
}
PrinterInfo thirdPrinter = new PrinterInfo.Builder(thirdPrinterId,
"Third printer", PrinterInfo.STATUS_IDLE)
.setCapabilities(thirdCapabilities)
.build();
printers.add(thirdPrinter);
session.addPrinters(printers);
}
return null;
}, null, null, null, null, null, invocation -> {
// Take a note onDestroy was called.
onPrinterDiscoverySessionDestroyCalled();
return null;
});
}
private PrintServiceCallbacks createFirstMockPrintServiceCallbacks() {
final PrinterDiscoverySessionCallbacks callbacks =
createFirstMockDiscoverySessionCallbacks();
return createMockPrintServiceCallbacks(invocation -> callbacks, invocation -> {
PrintJob printJob = (PrintJob) invocation.getArguments()[0];
printJob.complete();
return null;
}, null);
}
private PrintServiceCallbacks createSecondMockPrintServiceCallbacks() {
return createMockPrintServiceCallbacks(null, null, null);
}
}