Loading core/tests/coretests/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -1329,6 +1329,11 @@ android:exported="true"> </activity> <activity android:name="android.print.mockservice.AddPrintersActivity" android:exported="true"> </activity> </application> <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" Loading core/tests/coretests/res/xml/printservice.xml +2 −1 Original line number Diff line number Diff line Loading @@ -17,4 +17,5 @@ --> <print-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="android.print.mockservice.SettingsActivity"/> android:settingsActivity="android.print.mockservice.SettingsActivity" android:addPrintersActivity="android.print.mockservice.AddPrintersActivity" /> core/tests/coretests/src/android/print/BasePrintTest.java +36 −4 Original line number Diff line number Diff line Loading @@ -57,8 +57,7 @@ import java.util.concurrent.TimeoutException; * This is the base class for print tests. */ abstract class BasePrintTest { private static final long OPERATION_TIMEOUT = 30000; 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 Loading @@ -74,6 +73,39 @@ abstract class BasePrintTest { 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 * Loading Loading @@ -105,14 +137,14 @@ abstract class BasePrintTest { } @Before public void setUp() throws Exception { public void initCounters() throws Exception { // Initialize the latches. mStartCallCounter = new CallCounter(); mStartSessionCallCounter = new CallCounter(); } @After public void tearDown() throws Exception { public void exitActivities() throws Exception { // Exit print spooler getUiDevice().pressBack(); getUiDevice().pressBack(); Loading core/tests/coretests/src/android/print/IPrintManagerParametersTest.java +3 −38 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.print.mockservice.StubbablePrinterDiscoverySession; import android.support.test.filters.LargeTest; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -219,50 +220,14 @@ public class IPrintManagerParametersTest extends BasePrintTest { return new PrinterId(getActivity().getComponentName(), "dummy printer"); } @Override public void setUp() throws Exception { super.setUp(); @Before public void setUpMockService() throws Exception { MockPrintService.setCallbacks(createMockCallbacks()); mIPrintManager = IPrintManager.Stub .asInterface(ServiceManager.getService(Context.PRINT_SERVICE)); } /** * {@link Runnable} that can throw and {@link Exception} */ private 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 */ public void assertException(Invokable invokable, Class<? extends Exception> expectedClass) throws Exception { try { invokable.run(); } catch (Exception e) { if (e.getClass().isAssignableFrom(expectedClass)) { return; } else { throw new AssertionError("Expected: " + expectedClass.getName() + ", got: " + e.getClass().getName()); } } throw new AssertionError("No exception thrown"); } /** * test IPrintManager.getPrintJobInfo */ Loading core/tests/coretests/src/android/print/WorkflowTest.java 0 → 100644 +391 −0 Original line number Diff line number Diff line /* * 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 android.graphics.pdf.PdfDocument; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.print.mockservice.AddPrintersActivity; import android.print.mockservice.MockPrintService; import android.print.mockservice.PrinterDiscoverySessionCallbacks; import android.print.mockservice.StubbablePrinterDiscoverySession; import android.print.pdf.PrintedPdfDocument; import android.support.test.filters.LargeTest; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.util.Log; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; /** * Tests for the basic printing workflows */ public class WorkflowTest extends BasePrintTest { private static final String LOG_TAG = WorkflowTest.class.getSimpleName(); private static float sWindowAnimationScaleBefore; private static float sTransitionAnimationScaleBefore; private static float sAnimatiorDurationScaleBefore; interface InterruptableConsumer<T> { void accept(T t) throws InterruptedException; } /** * Execute {@code waiter} until {@code condition} is met. * * @param condition Conditions to wait for * @param waiter Code to execute while waiting */ private void waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter) throws TimeoutException, InterruptedException { long startTime = System.currentTimeMillis(); while (condition.get()) { long timeLeft = OPERATION_TIMEOUT - (System.currentTimeMillis() - startTime); if (timeLeft < 0) { throw new TimeoutException(); } waiter.accept(timeLeft); } } /** * Executes a shell command using shell user identity, and return the standard output in * string. * * @param cmd the command to run * * @return the standard output of the command */ private static String runShellCommand(String cmd) throws IOException { try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream( getInstrumentation().getUiAutomation().executeShellCommand(cmd))) { byte[] buf = new byte[64]; int bytesRead; StringBuilder stdout = new StringBuilder(); while ((bytesRead = is.read(buf)) != -1) { stdout.append(new String(buf, 0, bytesRead)); } return stdout.toString(); } } @BeforeClass public static void disableAnimations() throws Exception { try { sWindowAnimationScaleBefore = Float.parseFloat(runShellCommand( "settings get global window_animation_scale")); runShellCommand("settings put global window_animation_scale 0"); } catch (NumberFormatException e) { sWindowAnimationScaleBefore = Float.NaN; } try { sTransitionAnimationScaleBefore = Float.parseFloat(runShellCommand( "settings get global transition_animation_scale")); runShellCommand("settings put global transition_animation_scale 0"); } catch (NumberFormatException e) { sTransitionAnimationScaleBefore = Float.NaN; } try { sAnimatiorDurationScaleBefore = Float.parseFloat(runShellCommand( "settings get global animator_duration_scale")); runShellCommand("settings put global animator_duration_scale 0"); } catch (NumberFormatException e) { sAnimatiorDurationScaleBefore = Float.NaN; } } @AfterClass public static void enableAnimations() throws Exception { if (sWindowAnimationScaleBefore != Float.NaN) { runShellCommand( "settings put global window_animation_scale " + sWindowAnimationScaleBefore); } if (sTransitionAnimationScaleBefore != Float.NaN) { runShellCommand( "settings put global transition_animation_scale " + sTransitionAnimationScaleBefore); } if (sAnimatiorDurationScaleBefore != Float.NaN) { runShellCommand( "settings put global animator_duration_scale " + sAnimatiorDurationScaleBefore); } } /** Add a printer with a given name and supported mediasize to a session */ private void addPrinter(StubbablePrinterDiscoverySession session, String name, PrintAttributes.MediaSize mediaSize) { PrinterId printerId = session.getService().generatePrinterId(name); List<PrinterInfo> printers = new ArrayList<>(1); PrinterCapabilitiesInfo.Builder builder = new PrinterCapabilitiesInfo.Builder(printerId); builder.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0)) .setColorModes(PrintAttributes.COLOR_MODE_COLOR, PrintAttributes.COLOR_MODE_COLOR) .addMediaSize(mediaSize, true) .addResolution(new PrintAttributes.Resolution("300x300", "300x300", 300, 300), true); printers.add(new PrinterInfo.Builder(printerId, name, PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build()); session.addPrinters(printers); } /** Find a certain element in the UI and click on it */ private void clickOn(UiSelector selector) throws UiObjectNotFoundException { Log.i(LOG_TAG, "Click on " + selector); UiObject view = getUiDevice().findObject(selector); view.click(); getUiDevice().waitForIdle(); } /** Find a certain text in the UI and click on it */ private void clickOnText(String text) throws UiObjectNotFoundException { clickOn(new UiSelector().text(text)); } /** Set the printer in the print activity */ private void setPrinter(String printerName) throws UiObjectNotFoundException { clickOn(new UiSelector().resourceId("com.android.printspooler:id/destination_spinner")); clickOnText(printerName); } /** * Init mock print servic that returns a single printer by default. * * @param sessionRef Where to store the reference to the session once started */ private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef) { MockPrintService.setCallbacks(createMockPrintServiceCallbacks( inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> { synchronized (sessionRef) { sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock()) .getSession(); addPrinter(sessionRef[0], "1st printer", PrintAttributes.MediaSize.ISO_A0); sessionRef.notifyAll(); } return null; }, null, null, null, null, null, inv2 -> { synchronized (sessionRef) { sessionRef[0] = null; sessionRef.notifyAll(); } return null; } ), null, null)); } /** * Start print operation that just prints a single empty page * * @param printAttributesRef Where to store the reference to the print attributes once started */ private void print(PrintAttributes[] printAttributesRef) { print(new PrintDocumentAdapter() { @Override public void onStart() { } @Override public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { callback.onLayoutFinished((new PrintDocumentInfo.Builder("doc")).build(), !newAttributes.equals(printAttributesRef[0])); synchronized (printAttributesRef) { printAttributesRef[0] = newAttributes; printAttributesRef.notifyAll(); } } @Override public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { try { try { PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), printAttributesRef[0]); try { PdfDocument.Page page = document.startPage(0); document.finishPage(page); try (FileOutputStream os = new FileOutputStream( destination.getFileDescriptor())) { document.writeTo(os); os.flush(); } } finally { document.close(); } } finally { destination.close(); } callback.onWriteFinished(pages); } catch (IOException e) { callback.onWriteFailed(e.getMessage()); } } }, null); } @Test @LargeTest public void addAndSelectPrinter() throws Exception { final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1]; final PrintAttributes printAttributes[] = new PrintAttributes[1]; setMockPrintServiceCallbacks(session); print(printAttributes); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for session"); synchronized (session) { waitWithTimeout(() -> session[0] == null, session::wait); } setPrinter("1st printer"); Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A0), printAttributes::wait); } setPrinter("All printers\u2026"); // We are now in the SelectPrinterActivity clickOnText("Add printer"); // We are now in the AddPrinterActivity AddPrintersActivity.addObserver( () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1)); // This executes the observer registered above clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName()) .resourceId("com.android.printspooler:id/title")); getUiDevice().pressBack(); AddPrintersActivity.clearObservers(); // We are now in the SelectPrinterActivity clickOnText("2nd printer"); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A1), printAttributes::wait); } getUiDevice().pressBack(); // We are back in the test activity Log.i(LOG_TAG, "Waiting for session to end"); synchronized (session) { waitWithTimeout(() -> session[0] != null, session::wait); } } @Test @LargeTest public void abortSelectingPrinter() throws Exception { final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1]; final PrintAttributes printAttributes[] = new PrintAttributes[1]; setMockPrintServiceCallbacks(session); print(printAttributes); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for session"); synchronized (session) { waitWithTimeout(() -> session[0] == null, session::wait); } setPrinter("1st printer"); Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A0), printAttributes::wait); } setPrinter("All printers\u2026"); // We are now in the SelectPrinterActivity clickOnText("Add printer"); // We are now in the AddPrinterActivity AddPrintersActivity.addObserver( () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1)); // This executes the observer registered above clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName()) .resourceId("com.android.printspooler:id/title")); getUiDevice().pressBack(); AddPrintersActivity.clearObservers(); // Do not select a new printer, just press back getUiDevice().pressBack(); // We are now in the PrintActivity // The media size should not change Log.i(LOG_TAG, "Make sure print attributes did not change"); Thread.sleep(100); assertEquals(PrintAttributes.MediaSize.ISO_A0, printAttributes[0].getMediaSize()); getUiDevice().pressBack(); // We are back in the test activity Log.i(LOG_TAG, "Waiting for session to end"); synchronized (session) { waitWithTimeout(() -> session[0] != null, session::wait); } } } Loading
core/tests/coretests/AndroidManifest.xml +5 −0 Original line number Diff line number Diff line Loading @@ -1329,6 +1329,11 @@ android:exported="true"> </activity> <activity android:name="android.print.mockservice.AddPrintersActivity" android:exported="true"> </activity> </application> <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner" Loading
core/tests/coretests/res/xml/printservice.xml +2 −1 Original line number Diff line number Diff line Loading @@ -17,4 +17,5 @@ --> <print-service xmlns:android="http://schemas.android.com/apk/res/android" android:settingsActivity="android.print.mockservice.SettingsActivity"/> android:settingsActivity="android.print.mockservice.SettingsActivity" android:addPrintersActivity="android.print.mockservice.AddPrintersActivity" />
core/tests/coretests/src/android/print/BasePrintTest.java +36 −4 Original line number Diff line number Diff line Loading @@ -57,8 +57,7 @@ import java.util.concurrent.TimeoutException; * This is the base class for print tests. */ abstract class BasePrintTest { private static final long OPERATION_TIMEOUT = 30000; 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 Loading @@ -74,6 +73,39 @@ abstract class BasePrintTest { 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 * Loading Loading @@ -105,14 +137,14 @@ abstract class BasePrintTest { } @Before public void setUp() throws Exception { public void initCounters() throws Exception { // Initialize the latches. mStartCallCounter = new CallCounter(); mStartSessionCallCounter = new CallCounter(); } @After public void tearDown() throws Exception { public void exitActivities() throws Exception { // Exit print spooler getUiDevice().pressBack(); getUiDevice().pressBack(); Loading
core/tests/coretests/src/android/print/IPrintManagerParametersTest.java +3 −38 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ import android.print.mockservice.StubbablePrinterDiscoverySession; import android.support.test.filters.LargeTest; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; Loading Loading @@ -219,50 +220,14 @@ public class IPrintManagerParametersTest extends BasePrintTest { return new PrinterId(getActivity().getComponentName(), "dummy printer"); } @Override public void setUp() throws Exception { super.setUp(); @Before public void setUpMockService() throws Exception { MockPrintService.setCallbacks(createMockCallbacks()); mIPrintManager = IPrintManager.Stub .asInterface(ServiceManager.getService(Context.PRINT_SERVICE)); } /** * {@link Runnable} that can throw and {@link Exception} */ private 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 */ public void assertException(Invokable invokable, Class<? extends Exception> expectedClass) throws Exception { try { invokable.run(); } catch (Exception e) { if (e.getClass().isAssignableFrom(expectedClass)) { return; } else { throw new AssertionError("Expected: " + expectedClass.getName() + ", got: " + e.getClass().getName()); } } throw new AssertionError("No exception thrown"); } /** * test IPrintManager.getPrintJobInfo */ Loading
core/tests/coretests/src/android/print/WorkflowTest.java 0 → 100644 +391 −0 Original line number Diff line number Diff line /* * 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 android.graphics.pdf.PdfDocument; import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.print.mockservice.AddPrintersActivity; import android.print.mockservice.MockPrintService; import android.print.mockservice.PrinterDiscoverySessionCallbacks; import android.print.mockservice.StubbablePrinterDiscoverySession; import android.print.pdf.PrintedPdfDocument; import android.support.test.filters.LargeTest; import android.support.test.uiautomator.UiObject; import android.support.test.uiautomator.UiObjectNotFoundException; import android.support.test.uiautomator.UiSelector; import android.util.Log; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; import static org.junit.Assert.assertEquals; /** * Tests for the basic printing workflows */ public class WorkflowTest extends BasePrintTest { private static final String LOG_TAG = WorkflowTest.class.getSimpleName(); private static float sWindowAnimationScaleBefore; private static float sTransitionAnimationScaleBefore; private static float sAnimatiorDurationScaleBefore; interface InterruptableConsumer<T> { void accept(T t) throws InterruptedException; } /** * Execute {@code waiter} until {@code condition} is met. * * @param condition Conditions to wait for * @param waiter Code to execute while waiting */ private void waitWithTimeout(Supplier<Boolean> condition, InterruptableConsumer<Long> waiter) throws TimeoutException, InterruptedException { long startTime = System.currentTimeMillis(); while (condition.get()) { long timeLeft = OPERATION_TIMEOUT - (System.currentTimeMillis() - startTime); if (timeLeft < 0) { throw new TimeoutException(); } waiter.accept(timeLeft); } } /** * Executes a shell command using shell user identity, and return the standard output in * string. * * @param cmd the command to run * * @return the standard output of the command */ private static String runShellCommand(String cmd) throws IOException { try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream( getInstrumentation().getUiAutomation().executeShellCommand(cmd))) { byte[] buf = new byte[64]; int bytesRead; StringBuilder stdout = new StringBuilder(); while ((bytesRead = is.read(buf)) != -1) { stdout.append(new String(buf, 0, bytesRead)); } return stdout.toString(); } } @BeforeClass public static void disableAnimations() throws Exception { try { sWindowAnimationScaleBefore = Float.parseFloat(runShellCommand( "settings get global window_animation_scale")); runShellCommand("settings put global window_animation_scale 0"); } catch (NumberFormatException e) { sWindowAnimationScaleBefore = Float.NaN; } try { sTransitionAnimationScaleBefore = Float.parseFloat(runShellCommand( "settings get global transition_animation_scale")); runShellCommand("settings put global transition_animation_scale 0"); } catch (NumberFormatException e) { sTransitionAnimationScaleBefore = Float.NaN; } try { sAnimatiorDurationScaleBefore = Float.parseFloat(runShellCommand( "settings get global animator_duration_scale")); runShellCommand("settings put global animator_duration_scale 0"); } catch (NumberFormatException e) { sAnimatiorDurationScaleBefore = Float.NaN; } } @AfterClass public static void enableAnimations() throws Exception { if (sWindowAnimationScaleBefore != Float.NaN) { runShellCommand( "settings put global window_animation_scale " + sWindowAnimationScaleBefore); } if (sTransitionAnimationScaleBefore != Float.NaN) { runShellCommand( "settings put global transition_animation_scale " + sTransitionAnimationScaleBefore); } if (sAnimatiorDurationScaleBefore != Float.NaN) { runShellCommand( "settings put global animator_duration_scale " + sAnimatiorDurationScaleBefore); } } /** Add a printer with a given name and supported mediasize to a session */ private void addPrinter(StubbablePrinterDiscoverySession session, String name, PrintAttributes.MediaSize mediaSize) { PrinterId printerId = session.getService().generatePrinterId(name); List<PrinterInfo> printers = new ArrayList<>(1); PrinterCapabilitiesInfo.Builder builder = new PrinterCapabilitiesInfo.Builder(printerId); builder.setMinMargins(new PrintAttributes.Margins(0, 0, 0, 0)) .setColorModes(PrintAttributes.COLOR_MODE_COLOR, PrintAttributes.COLOR_MODE_COLOR) .addMediaSize(mediaSize, true) .addResolution(new PrintAttributes.Resolution("300x300", "300x300", 300, 300), true); printers.add(new PrinterInfo.Builder(printerId, name, PrinterInfo.STATUS_IDLE).setCapabilities(builder.build()).build()); session.addPrinters(printers); } /** Find a certain element in the UI and click on it */ private void clickOn(UiSelector selector) throws UiObjectNotFoundException { Log.i(LOG_TAG, "Click on " + selector); UiObject view = getUiDevice().findObject(selector); view.click(); getUiDevice().waitForIdle(); } /** Find a certain text in the UI and click on it */ private void clickOnText(String text) throws UiObjectNotFoundException { clickOn(new UiSelector().text(text)); } /** Set the printer in the print activity */ private void setPrinter(String printerName) throws UiObjectNotFoundException { clickOn(new UiSelector().resourceId("com.android.printspooler:id/destination_spinner")); clickOnText(printerName); } /** * Init mock print servic that returns a single printer by default. * * @param sessionRef Where to store the reference to the session once started */ private void setMockPrintServiceCallbacks(StubbablePrinterDiscoverySession[] sessionRef) { MockPrintService.setCallbacks(createMockPrintServiceCallbacks( inv -> createMockPrinterDiscoverySessionCallbacks(inv2 -> { synchronized (sessionRef) { sessionRef[0] = ((PrinterDiscoverySessionCallbacks) inv2.getMock()) .getSession(); addPrinter(sessionRef[0], "1st printer", PrintAttributes.MediaSize.ISO_A0); sessionRef.notifyAll(); } return null; }, null, null, null, null, null, inv2 -> { synchronized (sessionRef) { sessionRef[0] = null; sessionRef.notifyAll(); } return null; } ), null, null)); } /** * Start print operation that just prints a single empty page * * @param printAttributesRef Where to store the reference to the print attributes once started */ private void print(PrintAttributes[] printAttributesRef) { print(new PrintDocumentAdapter() { @Override public void onStart() { } @Override public void onLayout(PrintAttributes oldAttributes, PrintAttributes newAttributes, CancellationSignal cancellationSignal, LayoutResultCallback callback, Bundle extras) { callback.onLayoutFinished((new PrintDocumentInfo.Builder("doc")).build(), !newAttributes.equals(printAttributesRef[0])); synchronized (printAttributesRef) { printAttributesRef[0] = newAttributes; printAttributesRef.notifyAll(); } } @Override public void onWrite(PageRange[] pages, ParcelFileDescriptor destination, CancellationSignal cancellationSignal, WriteResultCallback callback) { try { try { PrintedPdfDocument document = new PrintedPdfDocument(getActivity(), printAttributesRef[0]); try { PdfDocument.Page page = document.startPage(0); document.finishPage(page); try (FileOutputStream os = new FileOutputStream( destination.getFileDescriptor())) { document.writeTo(os); os.flush(); } } finally { document.close(); } } finally { destination.close(); } callback.onWriteFinished(pages); } catch (IOException e) { callback.onWriteFailed(e.getMessage()); } } }, null); } @Test @LargeTest public void addAndSelectPrinter() throws Exception { final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1]; final PrintAttributes printAttributes[] = new PrintAttributes[1]; setMockPrintServiceCallbacks(session); print(printAttributes); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for session"); synchronized (session) { waitWithTimeout(() -> session[0] == null, session::wait); } setPrinter("1st printer"); Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A0), printAttributes::wait); } setPrinter("All printers\u2026"); // We are now in the SelectPrinterActivity clickOnText("Add printer"); // We are now in the AddPrinterActivity AddPrintersActivity.addObserver( () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1)); // This executes the observer registered above clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName()) .resourceId("com.android.printspooler:id/title")); getUiDevice().pressBack(); AddPrintersActivity.clearObservers(); // We are now in the SelectPrinterActivity clickOnText("2nd printer"); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A1), printAttributes::wait); } getUiDevice().pressBack(); // We are back in the test activity Log.i(LOG_TAG, "Waiting for session to end"); synchronized (session) { waitWithTimeout(() -> session[0] != null, session::wait); } } @Test @LargeTest public void abortSelectingPrinter() throws Exception { final StubbablePrinterDiscoverySession session[] = new StubbablePrinterDiscoverySession[1]; final PrintAttributes printAttributes[] = new PrintAttributes[1]; setMockPrintServiceCallbacks(session); print(printAttributes); // We are now in the PrintActivity Log.i(LOG_TAG, "Waiting for session"); synchronized (session) { waitWithTimeout(() -> session[0] == null, session::wait); } setPrinter("1st printer"); Log.i(LOG_TAG, "Waiting for print attributes to change"); synchronized (printAttributes) { waitWithTimeout( () -> printAttributes[0] == null || !printAttributes[0].getMediaSize().equals( PrintAttributes.MediaSize.ISO_A0), printAttributes::wait); } setPrinter("All printers\u2026"); // We are now in the SelectPrinterActivity clickOnText("Add printer"); // We are now in the AddPrinterActivity AddPrintersActivity.addObserver( () -> addPrinter(session[0], "2nd printer", PrintAttributes.MediaSize.ISO_A1)); // This executes the observer registered above clickOn(new UiSelector().text(MockPrintService.class.getCanonicalName()) .resourceId("com.android.printspooler:id/title")); getUiDevice().pressBack(); AddPrintersActivity.clearObservers(); // Do not select a new printer, just press back getUiDevice().pressBack(); // We are now in the PrintActivity // The media size should not change Log.i(LOG_TAG, "Make sure print attributes did not change"); Thread.sleep(100); assertEquals(PrintAttributes.MediaSize.ISO_A0, printAttributes[0].getMediaSize()); getUiDevice().pressBack(); // We are back in the test activity Log.i(LOG_TAG, "Waiting for session to end"); synchronized (session) { waitWithTimeout(() -> session[0] != null, session::wait); } } }