Distribute registry as an asset

The registry is checked in as both a text proto and a binary proto,
however the binary proto is the only file shipped in the APK. The text
proto is used to validate the binary proto registry contents and build
time, i.e. the build will fail if they're out of sync with a message
about how to regenerate the binary proto.

*** Mean showmap private dirty bytes ***
* Control (no Cobalt): 11,735,204
* Registry asset     : 12,054,200
* Registry resource  : 14,473,298

* Collected with

```
adb shell am instrument -w -e class \
  android.platform.test.scenario.adservices.GetTopicsApiCallMicrobenchmark \
  -e process-names com.google.android.adservices.api \
  -e listener android.device.collectors.ShowmapSnapshotListener \
  -e iterations 25 \
  android.platform.test.scenario/androidx.test.runner.AndroidJUnitRunner
```

*** Mean topics API cold start latency ***
* Control (no Cobalt): 524
* Registry asset     : 543
* Registry resource  : 589

* Collected with

```
adb shell am instrument -w -e class \
  android.platform.test.scenario.adservices.GetTopicsApiCallMicrobenchmark \
  -e process-names com.google.android.adservices.api \
  -e listener android.device.collectors.TopicsLatencyCollector \
  -e iterations 25 \
  android.platform.test.scenario/androidx.test.runner.AndroidJUnitRunner
```

Bug: 298651580

Test: atest CobaltRegistryLoaderTest
Test: Deleted bytes from binary proto asset, build failed and saw
      message with instructions to update

Change-Id: Ic1c67f53908455c1014afb427cdca5f2277ede62
diff --git a/adservices/apk/assets/cobalt/cobalt_registry.binarypb b/adservices/apk/assets/cobalt/cobalt_registry.binarypb
new file mode 100644
index 0000000..3051b85
--- /dev/null
+++ b/adservices/apk/assets/cobalt/cobalt_registry.binarypb
Binary files differ
diff --git a/adservices/libraries/cobalt/proto/Android.bp b/adservices/libraries/cobalt/proto/Android.bp
index 75c3079..ea406e9 100644
--- a/adservices/libraries/cobalt/proto/Android.bp
+++ b/adservices/libraries/cobalt/proto/Android.bp
@@ -16,19 +16,6 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-// genrule for converting a text proto registry to a binary proto.
-genrule_defaults {
-    name: "gen_cobalt_registry_binarypb",
-    tools: ["aprotoc"],
-    cmd: "$(location aprotoc) --encode=cobalt.CobaltRegistry " +
-         "--proto_path=packages/modules/AdServices/adservices/libraries/cobalt/proto " +
-         "packages/modules/AdServices/adservices/libraries/cobalt/proto/cobalt_registry.proto " +
-         "< $(in) > $(out)",
-    visibility: [
-        "//packages/modules/AdServices/service-core"
-    ],
-}
-
 filegroup {
     name: "adservices-cobalt-proto",
     srcs: [
diff --git a/adservices/service-core/Android.bp b/adservices/service-core/Android.bp
index a68b7eb..e5d5b37 100644
--- a/adservices/service-core/Android.bp
+++ b/adservices/service-core/Android.bp
@@ -24,11 +24,37 @@
     out: ["com/android/adservices/service/stats/AdServicesStatsLog.java"],
 }
 
+// Converts the text proto Cobalt registry into a binary proto.
 genrule {
-    name: "cobalt_registry.binarypb",
-    defaults: ["gen_cobalt_registry_binarypb"],
-    srcs: [ "resources/cobalt_registry.textpb"],
+    name: "cobalt-registry-binarypb-gen",
+    srcs: ["resources/cobalt_registry.textpb"],
     out: ["cobalt_registry.binarypb"],
+    tools: ["aprotoc"],
+    cmd: "$(location aprotoc) --encode=cobalt.CobaltRegistry " +
+        "--proto_path=packages/modules/AdServices/adservices/libraries/cobalt/proto " +
+        "packages/modules/AdServices/adservices/libraries/cobalt/proto/cobalt_registry.proto " +
+        "< $(in) > $(out)",
+}
+
+// Outputs a class confirming the Cobalt registry asset is validated, conditioned on
+// the asset binary proto and text proto being the same.
+genrule {
+    name: "cobalt-registry-validated-java",
+    srcs: [
+        ":cobalt-registry-binarypb-gen",
+        "resources/CobaltRegistryValidated.java",
+    ],
+    // Compare the generated binary proto to the asset binary proto and fail if they
+    // are different. Additionally, print a message with instructions on how to update
+    // the asset binary proto.
+    cmd: "cp $(location resources/CobaltRegistryValidated.java) $(out) && " +
+        "cmp -s $(location :cobalt-registry-binarypb-gen) packages/modules/AdServices/adservices/apk/assets/cobalt/cobalt_registry.binarypb " +
+        "|| (echo -e '\\n\\n################################################################\\n#\\n" +
+        "#  ERROR: Binary registry is out of date.  To update it, run:\\n#\\n" +
+        "#  cp $(location :cobalt-registry-binarypb-gen) " +
+        "packages/modules/AdServices/adservices/apk/assets/cobalt/cobalt_registry.binarypb\\n#\\n" +
+        "################################################################\\n\\n' >&2 && false)",
+    out: ["com/android/adservices/cobalt/CobaltRegistryValidated.java"],
 }
 
 filegroup {
@@ -86,6 +112,7 @@
     srcs: [
         ":adservices-service-core-sources",
         ":adservices-service-core-jni-sources",
+        ":cobalt-registry-validated-java",
         ":statslog-adservices-java-gen",
     ],
     manifest: "AndroidManifest.xml",
@@ -99,9 +126,6 @@
     javacflags: [
         "-Aroom.schemaLocation=packages/modules/AdServices/adservices/service-core/schemas",
     ],
-    java_resources: [
-        ":cobalt_registry.binarypb",
-    ],
     libs: [
         "androidx.room_room-runtime",
         "framework-annotations-lib", // For @SystemApi, etc
diff --git a/adservices/service-core/java/com/android/adservices/cobalt/CobaltFactory.java b/adservices/service-core/java/com/android/adservices/cobalt/CobaltFactory.java
index 7069192..1384a1f 100644
--- a/adservices/service-core/java/com/android/adservices/cobalt/CobaltFactory.java
+++ b/adservices/service-core/java/com/android/adservices/cobalt/CobaltFactory.java
@@ -32,6 +32,7 @@
 import com.android.cobalt.observations.PrivacyGenerator;
 import com.android.cobalt.system.SystemClockImpl;
 import com.android.cobalt.system.SystemData;
+import com.android.internal.annotations.GuardedBy;
 
 import com.google.cobalt.CobaltRegistry;
 
@@ -41,6 +42,8 @@
 
 /** Factory for Cobalt's logger and periodic job implementations. */
 public final class CobaltFactory {
+    private static final Object SINGLETON_LOCK = new Object();
+
     /*
      * Use the prod pipeline because AdServices' reports are for either the DEBUG or GA release
      * stage and DEBUG is sufficient for local testing.
@@ -53,7 +56,10 @@
     private static DataService sSingletonDataService;
     private static SecureRandom sSingletonSecureRandom;
 
+    @GuardedBy("SINGLETON_LOCK")
     private static CobaltLogger sSingletonCobaltLogger;
+
+    @GuardedBy("SINGLETON_LOCK")
     private static CobaltPeriodicJob sSingletonCobaltPeriodicJob;
 
     /**
@@ -66,12 +72,12 @@
             throws CobaltInitializationException {
         Objects.requireNonNull(context);
         Objects.requireNonNull(flags);
-        synchronized (CobaltFactory.class) {
+        synchronized (SINGLETON_LOCK) {
             if (sSingletonCobaltLogger == null) {
                 sSingletonCobaltLogger =
                         sSingletonCobaltLogger =
                                 new CobaltLoggerImpl(
-                                        getRegistry(),
+                                        getRegistry(context),
                                         CobaltReleaseStages.getReleaseStage(
                                                 flags.getAdservicesReleaseStageForCobalt()),
                                         getDataService(context),
@@ -97,11 +103,11 @@
             @NonNull Context context, @NonNull Flags flags) throws CobaltInitializationException {
         Objects.requireNonNull(context);
         Objects.requireNonNull(flags);
-        synchronized (CobaltFactory.class) {
+        synchronized (SINGLETON_LOCK) {
             if (sSingletonCobaltPeriodicJob == null) {
                 sSingletonCobaltPeriodicJob =
                         new CobaltPeriodicJobImpl(
-                                getRegistry(),
+                                getRegistry(context),
                                 CobaltReleaseStages.getReleaseStage(
                                         flags.getAdservicesReleaseStageForCobalt()),
                                 getDataService(context),
@@ -128,9 +134,10 @@
     }
 
     @NonNull
-    private static CobaltRegistry getRegistry() throws CobaltInitializationException {
+    private static CobaltRegistry getRegistry(Context context)
+            throws CobaltInitializationException {
         if (sSingletonCobaltRegistry == null) {
-            sSingletonCobaltRegistry = CobaltRegistryLoader.getRegistry();
+            sSingletonCobaltRegistry = CobaltRegistryLoader.getRegistry(context);
         }
         return sSingletonCobaltRegistry;
     }
diff --git a/adservices/service-core/java/com/android/adservices/cobalt/CobaltRegistryLoader.java b/adservices/service-core/java/com/android/adservices/cobalt/CobaltRegistryLoader.java
index 76ff890..e7c9652 100644
--- a/adservices/service-core/java/com/android/adservices/cobalt/CobaltRegistryLoader.java
+++ b/adservices/service-core/java/com/android/adservices/cobalt/CobaltRegistryLoader.java
@@ -16,24 +16,38 @@
 
 package com.android.adservices.cobalt;
 
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.res.AssetManager;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 import com.google.cobalt.CobaltRegistry;
+import com.google.common.io.ByteStreams;
 
-/** Loads the Cobalt registry from a Java resource. */
-@VisibleForTesting
+import java.io.InputStream;
+
+/** Loads the Cobalt registry from a APK asset. */
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
 public final class CobaltRegistryLoader {
-    private static final String REGISTRY_FILE = "cobalt_registry.binarypb";
+    private static final String REGISTRY_ASSET_FILE = "cobalt/cobalt_registry.binarypb";
 
     /**
-     * Get the Cobalt registry from the JAR's resource file.
+     * Get the Cobalt registry from the APK asset directory.
      *
      * @return the CobaltRegistry
      */
-    public static CobaltRegistry getRegistry() throws CobaltInitializationException {
-        try {
-            final ClassLoader loader = CobaltRegistryLoader.class.getClassLoader();
-            return CobaltRegistry.parseFrom(loader.getResourceAsStream(REGISTRY_FILE));
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static CobaltRegistry getRegistry(@NonNull Context context)
+            throws CobaltInitializationException {
+        if (!CobaltRegistryValidated.IS_REGISTRY_VALIDATED) {
+            throw new AssertionError(
+                    "Cobalt registry was not validated at build time, something is very wrong");
+        }
+
+        AssetManager assetManager = context.getAssets();
+        try (InputStream inputStream = assetManager.open(REGISTRY_ASSET_FILE)) {
+            return CobaltRegistry.parseFrom(ByteStreams.toByteArray(inputStream));
         } catch (Exception e) {
             throw new CobaltInitializationException("Exception while reading registry", e);
         }
diff --git a/adservices/service-core/resources/CobaltRegistryValidated.java b/adservices/service-core/resources/CobaltRegistryValidated.java
new file mode 100644
index 0000000..c5f2db9
--- /dev/null
+++ b/adservices/service-core/resources/CobaltRegistryValidated.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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 com.android.adservices.cobalt;
+
+/**
+ * A class whose presence indicates the Cobalt registry was validated at build time.
+ *
+ * <p>This source file isn't included in `service-core` unless the build rule that validates the
+ * Cobalt registry completes successfully.
+ */
+final class CobaltRegistryValidated {
+    /**
+     * Value indicating the Cobalt binary proto registry was confirmed the same as the text proto
+     * registry at build-time.
+     *
+     * <p>This value will be missing if the registry wasn't validated because the build will fail.
+     */
+    public static final boolean IS_REGISTRY_VALIDATED = true;
+
+    private CobaltRegistryValidated() {}
+}
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltRegistryLoaderTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltRegistryLoaderTest.java
index 41a428b..4469754 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltRegistryLoaderTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/cobalt/CobaltRegistryLoaderTest.java
@@ -47,14 +47,19 @@
     private static final Context CONTEXT = ApplicationProvider.getApplicationContext();
 
     @Test
+    public void cobaltRegistryIsValidated_isTrue() throws Exception {
+        assertThat(CobaltRegistryValidated.IS_REGISTRY_VALIDATED).isTrue();
+    }
+
+    @Test
     public void getRegistry_registryCanBeLoaded() throws Exception {
-        CobaltRegistry registry = CobaltRegistryLoader.getRegistry();
+        CobaltRegistry registry = CobaltRegistryLoader.getRegistry(CONTEXT);
         assertThat(registry).isNotEqualTo(CobaltRegistry.getDefaultInstance());
     }
 
     @Test
     public void getRegistry_unsupportedFeaturesNotInRegistry() throws Exception {
-        CobaltRegistry registry = CobaltRegistryLoader.getRegistry();
+        CobaltRegistry registry = CobaltRegistryLoader.getRegistry(CONTEXT);
         assertThat(registry.getCustomersCount()).isEqualTo(1);
         assertThat(registry.getCustomers(0).getProjectsCount()).isEqualTo(1);
 
diff --git a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/cobalt/TopicsCobaltLoggerTest.java b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/cobalt/TopicsCobaltLoggerTest.java
index c9b4f78..ca3ce62 100644
--- a/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/cobalt/TopicsCobaltLoggerTest.java
+++ b/adservices/tests/unittest/service-core/src/com/android/adservices/service/topics/cobalt/TopicsCobaltLoggerTest.java
@@ -24,6 +24,9 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import com.android.adservices.cobalt.CobaltRegistryLoader;
@@ -53,6 +56,8 @@
     private static final int LAST_TOPIC = FIRST_TOPIC + SUPPORTED_TOPICS_COUNT - 1;
     private static final int LOGGED_TOPICS_COUNT = 10;
 
+    private static final Context sContext = ApplicationProvider.getApplicationContext();
+
     @Mock private CobaltLogger mMockCobaltLogger;
     private TopicsCobaltLogger mTopicsCobaltLogger;
 
@@ -70,7 +75,7 @@
         // See
         // //packages/modules/AdServices/adservices/service-core/resources/cobalt_registry.textpb
         // for the actual registy.
-        CobaltRegistry cobaltRegistry = CobaltRegistryLoader.getRegistry();
+        CobaltRegistry cobaltRegistry = CobaltRegistryLoader.getRegistry(sContext);
         assertThat(cobaltRegistry.getCustomersCount()).isAtLeast(1);
         assertThat(cobaltRegistry.getCustomers(0).getProjectsCount()).isAtLeast(1);
         MetricDefinition topicsMetric =