Loading core/java/android/print/PrintDocumentInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ import java.lang.annotation.RetentionPolicy; */ public final class PrintDocumentInfo implements Parcelable { // LINT.IfChange /** * Constant for unknown page count. */ Loading Loading @@ -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; Loading packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +54 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading packages/PrintSpooler/src/com/android/printspooler/stats/StatsAsyncLogger.kt +592 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/PrintSpooler/src/com/android/printspooler/stats/StatsLogWrapper.kt +29 −0 Original line number Diff line number Diff line Loading @@ -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, ) } } packages/PrintSpooler/tests/robolectric/src/com/android/printspooler/stats/StatsAsyncLoggerTest.kt +141 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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) Loading Loading
core/java/android/print/PrintDocumentInfo.java +3 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,7 @@ import java.lang.annotation.RetentionPolicy; */ public final class PrintDocumentInfo implements Parcelable { // LINT.IfChange /** * Constant for unknown page count. */ Loading Loading @@ -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; Loading
packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +54 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); Loading
packages/PrintSpooler/src/com/android/printspooler/stats/StatsAsyncLogger.kt +592 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/PrintSpooler/src/com/android/printspooler/stats/StatsLogWrapper.kt +29 −0 Original line number Diff line number Diff line Loading @@ -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, ) } }
packages/PrintSpooler/tests/robolectric/src/com/android/printspooler/stats/StatsAsyncLoggerTest.kt +141 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -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 Loading Loading @@ -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) Loading