Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59b0fbd1 authored by Philip P. Moltmann's avatar Philip P. Moltmann
Browse files

Update print attributes when printer gets selected

Test: Add new non-CTS print workflow tests. These tests will cover
      general printing workflows such as the situation fixed in this
      change.
Change-Id: I33b6842bba164c45a6afbc09f2e0c9b0a523ef30
parent 2e0a36c4
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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"
+2 −1
Original line number Diff line number Diff line
@@ -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" />
+36 −4
Original line number Diff line number Diff line
@@ -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

@@ -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
     *
@@ -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();
+3 −38
Original line number Diff line number Diff line
@@ -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;

@@ -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
     */
+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