stats: Add PrinterDiscovery event

We want to record what printer connections are available for setup.

Add the necessary enums and event recording logic with tests.

Bug: 390478410
Test: atest BuiltInPrintServiceRobolectricTest
Test: validate logged events via adb and webui
Flag: com.android.bips.flags.printing_telemetry
Change-Id: Iab6f6a862cd9769d12eedc3fdd9f101e8fd86718
diff --git a/src/com/android/bips/discovery/ManualDiscovery.java b/src/com/android/bips/discovery/ManualDiscovery.java
index 4b8f9a1..77147ba 100644
--- a/src/com/android/bips/discovery/ManualDiscovery.java
+++ b/src/com/android/bips/discovery/ManualDiscovery.java
@@ -22,8 +22,10 @@
 import android.util.Log;
 
 import com.android.bips.BuiltInPrintService;
+import com.android.bips.flags.Flags;
 import com.android.bips.ipp.CapabilitiesCache;
 import com.android.bips.jni.LocalPrinterCapabilities;
+import com.android.bips.stats.StatsAsyncLogger;
 import com.android.bips.util.WifiMonitor;
 
 import java.util.ArrayList;
@@ -220,6 +222,12 @@
             }
             mAddRequests.remove(this);
             mFinalCallback.onFound(resolvedPrinter, capabilities.isSupported);
+            if (Flags.printingTelemetry()) {
+                StatsAsyncLogger.INSTANCE
+                         .PrinterDiscovery(
+                                  StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MANUAL,
+                                  resolvedPrinter.isSecure());
+            }
         }
 
         /** Stop all in-progress capability requests that are in progress */
diff --git a/src/com/android/bips/discovery/MdnsDiscovery.java b/src/com/android/bips/discovery/MdnsDiscovery.java
index 51c1781..3fe066f 100644
--- a/src/com/android/bips/discovery/MdnsDiscovery.java
+++ b/src/com/android/bips/discovery/MdnsDiscovery.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 
 import com.android.bips.BuiltInPrintService;
+import com.android.bips.flags.Flags;
+import com.android.bips.stats.StatsAsyncLogger;
 
 import java.net.Inet4Address;
 import java.util.ArrayList;
@@ -116,7 +118,18 @@
                 + "/" + resourcePath);
         String location = getStringAttribute(info, ATTRIBUTE_NOTE);
 
-        return new DiscoveredPrinter(uuidUri, info.getServiceName(), path, location);
+        if (Flags.printingTelemetry()) {
+            final DiscoveredPrinter discoveredPrinter = new DiscoveredPrinter(uuidUri,
+                                                                              info.getServiceName(),
+                                                                              path, location);
+            StatsAsyncLogger.INSTANCE
+                     .PrinterDiscovery(
+                              StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MDNS,
+                              discoveredPrinter.isSecure());
+            return discoveredPrinter;
+        } else {
+            return new DiscoveredPrinter(uuidUri, info.getServiceName(), path, location);
+        }
     }
 
     /** Return the value of an attribute or null if not present */
diff --git a/src/com/android/bips/p2p/P2pPrinterConnection.java b/src/com/android/bips/p2p/P2pPrinterConnection.java
index 7fd2be7..207941d 100644
--- a/src/com/android/bips/p2p/P2pPrinterConnection.java
+++ b/src/com/android/bips/p2p/P2pPrinterConnection.java
@@ -28,7 +28,9 @@
 import com.android.bips.discovery.DiscoveredPrinter;
 import com.android.bips.discovery.Discovery;
 import com.android.bips.discovery.P2pDiscovery;
+import com.android.bips.flags.Flags;
 import com.android.bips.jni.LocalPrinterCapabilities;
+import com.android.bips.stats.StatsAsyncLogger;
 
 import java.net.Inet4Address;
 import java.net.NetworkInterface;
@@ -68,6 +70,12 @@
         if (DEBUG) Log.d(TAG, "Connecting to " + P2pMonitor.toString(peer));
         // Initialize mPrinter to handle onPeerFound callback for re-discover cases
         mPrinter = toPrinter(peer);
+        if (Flags.printingTelemetry()) {
+            StatsAsyncLogger.INSTANCE
+                     .PrinterDiscovery(
+                              StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.P2P,
+                              mPrinter.isSecure());
+        }
         connectToPeer(peer);
     }
 
diff --git a/src/com/android/bips/stats/StatsAsyncLogger.kt b/src/com/android/bips/stats/StatsAsyncLogger.kt
index b87d8a5..cad9e8e 100644
--- a/src/com/android/bips/stats/StatsAsyncLogger.kt
+++ b/src/com/android/bips/stats/StatsAsyncLogger.kt
@@ -64,6 +64,41 @@
         eventHandler = handler
     }
 
+    fun PrinterDiscovery(
+        scheme: StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent,
+        secure: Boolean,
+    ): Boolean {
+        if (DEBUG) {
+            Log.d(TAG, "Logging PrinterDiscovery event")
+        }
+        synchronized(semaphore) {
+            if (!semaphore.tryAcquire()) {
+                Log.w(TAG, "Logging too many events, dropping PrinterDiscovery event")
+                return false
+            }
+            val result =
+                eventHandler.postAtTime(
+                    Runnable {
+                        synchronized(semaphore) {
+                            if (DEBUG) {
+                                Log.d(TAG, "Async logging PrinterDiscovery event")
+                            }
+                            statsLogWrapper.internalPrinterDiscovery(scheme, secure)
+                            semaphore.release()
+                        }
+                    },
+                    nextAvailableTimeMillis,
+                )
+            if (!result) {
+                Log.e(TAG, "Could not log PrinterDiscovery event")
+                semaphore.release()
+                return false
+            }
+            nextAvailableTimeMillis = getNextAvailableTimeMillis()
+        }
+        return true
+    }
+
     fun DiscoveredPrinterCapabilities(
         makeAndModel: String,
         colorModeMask: Int,
@@ -273,6 +308,28 @@
     // Most of these are internal to the package so are prefixed with
     // Internal
 
+    // PrinterDiscovery enums
+
+    // Not Internal because it is used by clients.
+    enum class DiscoverySchemePrinterDiscoveryEvent(val rawValue: Int) {
+        // Unspecified should never be used
+        UNSPECIFIED(
+            BipsStatsLog
+                .BIPS_PRINTER_DISCOVERY__DISCOVERY_SCHEME__BIPS_PRINTER_DISCOVERY_SCHEME_UNSPECIFIED
+        ),
+        MDNS(
+            BipsStatsLog
+                .BIPS_PRINTER_DISCOVERY__DISCOVERY_SCHEME__BIPS_PRINTER_DISCOVERY_SCHEME_MDNS
+        ),
+        MANUAL(
+            BipsStatsLog
+                .BIPS_PRINTER_DISCOVERY__DISCOVERY_SCHEME__BIPS_PRINTER_DISCOVERY_SCHEME_MANUAL
+        ),
+        P2P(
+            BipsStatsLog.BIPS_PRINTER_DISCOVERY__DISCOVERY_SCHEME__BIPS_PRINTER_DISCOVERY_SCHEME_P2P
+        ),
+    }
+
     // DiscoveredPrinterCapabilities enums
 
     enum class InternalMediaTypeDiscoveredPrinterCapsEvent(
diff --git a/src/com/android/bips/stats/StatsLogWrapper.kt b/src/com/android/bips/stats/StatsLogWrapper.kt
index 885594c..e8c45d5 100644
--- a/src/com/android/bips/stats/StatsLogWrapper.kt
+++ b/src/com/android/bips/stats/StatsLogWrapper.kt
@@ -37,6 +37,13 @@
         )
     }
 
+    open fun internalPrinterDiscovery(
+        scheme: StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent,
+        secure: Boolean,
+    ) {
+        BipsStatsLog.write(BipsStatsLog.BIPS_PRINTER_DISCOVERY, scheme.rawValue, secure)
+    }
+
     open fun internalDiscoveredPrinterCapabilities(
         makeAndModel: String,
         supportedColors: Set<StatsAsyncLogger.InternalColorModeDiscoveredPrinterCapsEvent>,
diff --git a/tests/robolectric/src/com/android/bips/stats/StatsAsyncLoggerTest.kt b/tests/robolectric/src/com/android/bips/stats/StatsAsyncLoggerTest.kt
index b9f1e97..53c4b09 100644
--- a/tests/robolectric/src/com/android/bips/stats/StatsAsyncLoggerTest.kt
+++ b/tests/robolectric/src/com/android/bips/stats/StatsAsyncLoggerTest.kt
@@ -61,6 +61,65 @@
     }
 
     @Test
+    fun printerDiscoverySuccessfullyLoggedTest() {
+        val logWrapperInOrder = inOrder(mStatsLogWrapper)
+        val handlerInOrder = inOrder(mHandler)
+        val semaphoreInOrder = inOrder(mSemaphore)
+        val timeCaptor = argumentCaptor<Long>()
+        val runnableCaptor = argumentCaptor<Runnable>()
+
+        // Arbitrary arguments
+        assertThat(
+                StatsAsyncLogger.PrinterDiscovery(
+                    StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MDNS,
+                    false,
+                )
+            )
+            .isTrue()
+        assertThat(
+                StatsAsyncLogger.PrinterDiscovery(
+                    StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.P2P,
+                    true,
+                )
+            )
+            .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)
+            .internalPrinterDiscovery(
+                StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MDNS,
+                false,
+            )
+        logWrapperInOrder
+            .verify(mStatsLogWrapper)
+            .internalPrinterDiscovery(
+                StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.P2P,
+                true,
+            )
+        logWrapperInOrder.verifyNoMoreInteractions()
+
+        // Validate Semaphore logic
+        semaphoreInOrder.verify(mSemaphore, times(2)).tryAcquire()
+        semaphoreInOrder.verify(mSemaphore, times(2)).release()
+    }
+
+    @Test
     fun discoveredPrinterCapsSuccessfullyLoggedTest() {
         val logWrapperInOrder = inOrder(mStatsLogWrapper)
         val handlerInOrder = inOrder(mHandler)
@@ -355,6 +414,13 @@
                 )
             )
             .isFalse()
+        assertThat(
+                StatsAsyncLogger.PrinterDiscovery(
+                    StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MDNS,
+                    false,
+                )
+            )
+            .isFalse()
         verifyNoInteractions(mHandler)
     }
 
@@ -381,7 +447,14 @@
                 StatsAsyncLogger.DiscoveredPrinterCapabilities("foo", 0, setOf(), 0, true, setOf())
             )
             .isFalse()
-        verify(mSemaphore, times(3)).release()
+        assertThat(
+                StatsAsyncLogger.PrinterDiscovery(
+                    StatsAsyncLogger.DiscoverySchemePrinterDiscoveryEvent.MDNS,
+                    false,
+                )
+            )
+            .isFalse()
+        verify(mSemaphore, times(4)).release()
     }
 
     @Test