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

Commit 36f65aa0 authored by Aaron Massey's avatar Aaron Massey
Browse files

stats: Add PrintJob event

We want to log stats about print jobs, like were they successful,
failed, cancelled and what were the job attributes.

Log print jobs when they are in final print spooler states.

Bug: 390478410
Test: atest PrintSpoolerRobolectricTests
Test: m PrintSpooler
Test: validate logged events via adb and webui
Flag: com.android.printspooler.flags.printing_telemetry
Change-Id: I7705e0cb7bf4d9c80584c6887ae99d3c32fa2ebc
parent 838afefa
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -78,6 +78,7 @@ import java.lang.annotation.RetentionPolicy;
 */
public final class PrintDocumentInfo implements Parcelable {

    // LINT.IfChange
    /**
     * Constant for unknown page count.
     */
@@ -119,6 +120,8 @@ public final class PrintDocumentInfo implements Parcelable {
     * </p>
     */
    public static final int CONTENT_TYPE_PHOTO = 1;
    // Update BuiltInPrintService stats logger too.
    // LINT.ThenChange(/packages/PrintSpooler/src/com/android/printspooler/stats/StatsAsyncLogger.kt)

    private @NonNull String mName;
    private @IntRange(from = -1) int mPageCount;
+54 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Binder;
@@ -554,6 +555,58 @@ public final class PrintSpoolerService extends Service {
        mNotificationController.onUpdateNotifications(mPrintJobs);
    }

    // Stats Logging
    private void logPrintJobFinalState(PrinterId printerId, PrintJobInfo printJob) {
        if (!Flags.printingTelemetry()) {
            return;
        }
        final ComponentName service = (printerId == null) ? null : printerId.getServiceName();
        if (service == null) {
            // We don't know what to do without an identifiable service.
            Log.e(LOG_TAG, "Failed to get service ComponentName");
            return;
        }
        int serviceUId = 0;
        try {
            serviceUId =
                getPackageManager().getApplicationInfo(service.getPackageName(), 0).uid;
        } catch (NameNotFoundException e) {
            Log.e(LOG_TAG, String.format("Failed to get uid for service=%s",
                                         service.flattenToString()), e);
            // We don't know what to do without an identifiable service.
            return;
        }
        final boolean savedPdf = service.getPackageName().startsWith(this.getPackageName());
        final int state = printJob.getState();

        // The following values are all optional.

        final PrintAttributes attributes = printJob.getAttributes();
        final PrintAttributes.MediaSize size = (attributes == null)
                ? null : attributes.getMediaSize();
        final PrintAttributes.Resolution resolution = (attributes == null)
                ? null : attributes.getResolution();
        final int colorMode = (attributes == null) ? 0 : attributes.getColorMode();
        final int duplexMode = (attributes == null) ? 0 : attributes.getDuplexMode();

        final PrintDocumentInfo docInfo = printJob.getDocumentInfo();
        final int pageCount = (docInfo == null)
                ? PrintDocumentInfo.PAGE_COUNT_UNKNOWN : docInfo.getPageCount();
        final int docType = (docInfo == null)
                ? PrintDocumentInfo.CONTENT_TYPE_UNKNOWN : docInfo.getContentType();

        StatsAsyncLogger.INSTANCE.PrintJob(serviceUId,
                                           state,
                                           colorMode,
                                           size,
                                           resolution,
                                           duplexMode,
                                           docType,
                                           savedPdf,
                                           pageCount);

    }

    public boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
        boolean success = false;

@@ -592,6 +645,7 @@ public final class PrintSpoolerService extends Service {
                    case PrintJobInfo.STATE_FAILED: {
                        PrinterId printerId = printJob.getPrinterId();
                        if (printerId != null) {
                            logPrintJobFinalState(printerId, printJob);
                            ComponentName service = printerId.getServiceName();
                            if (!hasActivePrintJobsForServiceLocked(service)) {
                                sendOnAllPrintJobsForServiceHandled(service);
+592 −0

File changed.

Preview size limit exceeded, changes collapsed.

+29 −0
Original line number Diff line number Diff line
@@ -68,4 +68,33 @@ open class StatsLogWrapper {
            duplexModes,
        )
    }

    open fun internalPrintJob(
        @UserIdInt printServiceId: Int,
        finalState: StatsAsyncLogger.InternalFinalStatePrintJobEvent,
        colorMode: StatsAsyncLogger.InternalColorModePrintJobEvent,
        duplexMode: StatsAsyncLogger.InternalDuplexModePrintJobEvent,
        mediaSize: StatsAsyncLogger.InternalMediaSizePrintJobEvent,
        docType: StatsAsyncLogger.InternalDocumentTypePrintJobEvent,
        orientation: StatsAsyncLogger.InternalOrientationPrintJobEvent,
        horizontalDpi: Int,
        verticalDpi: Int,
        savedPdf: Boolean,
        pageCount: Int,
    ) {
        PrintSpoolerStatsLog.write(
            PrintSpoolerStatsLog.FRAMEWORK_PRINT_JOB,
            finalState.rawValue,
            colorMode.rawValue,
            printServiceId,
            mediaSize.rawValue,
            horizontalDpi,
            verticalDpi,
            orientation.rawValue,
            duplexMode.rawValue,
            docType.rawValue,
            savedPdf,
            pageCount,
        )
    }
}
+141 −1
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ package com.android.printspooler.stats

import android.os.Handler
import android.print.PrintAttributes
import android.print.PrintDocumentInfo
import android.print.PrintJobInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Semaphore
@@ -62,6 +64,116 @@ open class StatsAsyncLoggerTest {
        StatsAsyncLogger.stopLogging()
    }

    @Test
    fun printJobSuccessfullyLoggedTest() {
        val logWrapperInOrder = inOrder(mStatsLogWrapper)
        val handlerInOrder = inOrder(mHandler)
        val semaphoreInOrder = inOrder(mSemaphore)
        val timeCaptor = argumentCaptor<Long>()
        val runnableCaptor = argumentCaptor<Runnable>()

        StatsAsyncLogger.startLogging()
        StatsAsyncLogger.testSetSemaphore(mSemaphore)
        StatsAsyncLogger.testSetHandler(mHandler)
        StatsAsyncLogger.testSetStatsLogWrapper(mStatsLogWrapper)

        // "foo" printer service: Generally arbitrary arguments focusing more on creating non-empty
        // lists.
        val printServiceFoo = 42
        val colorsMaskFoo = PrintAttributes.COLOR_MODE_COLOR
        val sizeFoo = PrintAttributes.MediaSize.NA_LETTER
        val duplexModeMaskFoo = PrintAttributes.DUPLEX_MODE_LONG_EDGE
        val resolutionFoo = PrintAttributes.Resolution("hello", "resolution", 123, 321)
        val docTypeFoo = PrintDocumentInfo.CONTENT_TYPE_DOCUMENT
        val savedPdfFoo = true
        val pageCount = 52
        val finalState = PrintJobInfo.STATE_COMPLETED
        assertThat(
                StatsAsyncLogger.PrintJob(
                    printServiceFoo,
                    finalState,
                    colorsMaskFoo,
                    sizeFoo,
                    resolutionFoo,
                    duplexModeMaskFoo,
                    docTypeFoo,
                    savedPdfFoo,
                    pageCount,
                )
            )
            .isTrue()

        // "bar" printer service: Generally arbitrary arguments focusing more on empty/default
        // values.
        val printServiceBar = 1337
        assertThat(
                StatsAsyncLogger.PrintJob(
                    printServiceBar,
                    PrintJobInfo.STATE_FAILED,
                    0,
                    null,
                    null,
                    0,
                    PrintDocumentInfo.CONTENT_TYPE_UNKNOWN,
                    false,
                    PrintDocumentInfo.PAGE_COUNT_UNKNOWN,
                )
            )
            .isTrue()

        handlerInOrder
            .verify(mHandler, times(2))
            .postAtTime(runnableCaptor.capture(), timeCaptor.capture())
        handlerInOrder.verifyNoMoreInteractions()

        // Validate delay args
        val firstTime = timeCaptor.firstValue
        val secondTime = timeCaptor.secondValue
        assertThat(secondTime - firstTime)
            .isAtLeast(StatsAsyncLogger.EVENT_REPORTED_MIN_INTERVAL.inWholeMilliseconds)
        assertThat(secondTime - firstTime)
            .isAtMost(2 * StatsAsyncLogger.EVENT_REPORTED_MIN_INTERVAL.inWholeMilliseconds)

        // Validate Runnable logic
        runnableCaptor.firstValue.run()
        runnableCaptor.secondValue.run()
        logWrapperInOrder
            .verify(mStatsLogWrapper)
            .internalPrintJob(
                printServiceFoo,
                StatsAsyncLogger.InternalFinalStatePrintJobEvent.COMPLETED,
                StatsAsyncLogger.InternalColorModePrintJobEvent.COLOR,
                StatsAsyncLogger.InternalDuplexModePrintJobEvent.LONG_EDGE,
                StatsAsyncLogger.InternalMediaSizePrintJobEvent.NA_LETTER,
                StatsAsyncLogger.InternalDocumentTypePrintJobEvent.DOCUMENT,
                StatsAsyncLogger.InternalOrientationPrintJobEvent.PORTRAIT,
                resolutionFoo.getHorizontalDpi(),
                resolutionFoo.getVerticalDpi(),
                savedPdfFoo,
                pageCount,
            )
        logWrapperInOrder
            .verify(mStatsLogWrapper)
            .internalPrintJob(
                printServiceBar,
                StatsAsyncLogger.InternalFinalStatePrintJobEvent.FAILED,
                StatsAsyncLogger.InternalColorModePrintJobEvent.UNSPECIFIED,
                StatsAsyncLogger.InternalDuplexModePrintJobEvent.UNSPECIFIED,
                StatsAsyncLogger.InternalMediaSizePrintJobEvent.UNSPECIFIED,
                StatsAsyncLogger.InternalDocumentTypePrintJobEvent.UNSPECIFIED,
                StatsAsyncLogger.InternalOrientationPrintJobEvent.UNSPECIFIED,
                0,
                0,
                false,
                PrintDocumentInfo.PAGE_COUNT_UNKNOWN,
            )
        logWrapperInOrder.verifyNoMoreInteractions()

        // Validate Semaphore logic
        semaphoreInOrder.verify(mSemaphore, times(2)).tryAcquire()
        semaphoreInOrder.verify(mSemaphore, times(2)).release()
    }

    @Test
    fun printerDiscoverySuccessfullyLoggedTest() {
        val logWrapperInOrder = inOrder(mStatsLogWrapper)
@@ -255,7 +367,21 @@ open class StatsAsyncLoggerTest {
        assertThat(StatsAsyncLogger.AdvancedOptionsUiLaunched(42)).isFalse()
        assertThat(StatsAsyncLogger.MainPrintUiLaunched(setOf(1, 2, 3), 42)).isFalse()
        assertThat(StatsAsyncLogger.PrinterDiscovery(1337, 0, 0, setOf())).isFalse()
        verify(mSemaphore, times(3)).release()
        assertThat(
                StatsAsyncLogger.PrintJob(
                    42,
                    PrintJobInfo.STATE_FAILED,
                    0,
                    null,
                    null,
                    0,
                    PrintDocumentInfo.CONTENT_TYPE_UNKNOWN,
                    false,
                    PrintDocumentInfo.PAGE_COUNT_UNKNOWN,
                )
            )
            .isFalse()
        verify(mSemaphore, times(4)).release()
    }

    @Test
@@ -289,6 +415,20 @@ open class StatsAsyncLoggerTest {
        assertThat(StatsAsyncLogger.AdvancedOptionsUiLaunched(42)).isFalse()
        assertThat(StatsAsyncLogger.MainPrintUiLaunched(setOf(1, 2, 3), 42)).isFalse()
        assertThat(StatsAsyncLogger.PrinterDiscovery(1337, 0, 0, setOf())).isFalse()
        assertThat(
                StatsAsyncLogger.PrintJob(
                    42,
                    PrintJobInfo.STATE_FAILED,
                    0,
                    null,
                    null,
                    0,
                    PrintDocumentInfo.CONTENT_TYPE_UNKNOWN,
                    false,
                    PrintDocumentInfo.PAGE_COUNT_UNKNOWN,
                )
            )
            .isFalse()
        verifyNoInteractions(mHandler)
        verifyNoInteractions(mSemaphore)
        verifyNoInteractions(mStatsLogWrapper)