Merge "Write fingerprint to package map." into main
diff --git a/backported_fixes/Android.bp b/backported_fixes/Android.bp
new file mode 100644
index 0000000..a20f3fc
--- /dev/null
+++ b/backported_fixes/Android.bp
@@ -0,0 +1,115 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_android_media_reliability",
+}
+
+genrule {
+    name: "applied_backported_fixes",
+    tools: ["applied_backported_fixes_main"],
+    srcs: [":applied_backported_fix_binpbs"],
+    out: ["applied_backported_fixes.prop"],
+    cmd: "$(location applied_backported_fixes_main)" +
+        " -p $(location applied_backported_fixes.prop)" +
+        " $(in)",
+}
+
+java_library {
+    name: "backported_fixes_proto",
+    srcs: [
+        "backported_fixes.proto",
+    ],
+    host_supported: true,
+}
+
+java_library {
+    name: "backported_fixes_common",
+    srcs: ["src/java/com/android/build/backportedfixes/common/*.java"],
+    static_libs: [
+        "backported_fixes_proto",
+        "guava",
+    ],
+    host_supported: true,
+}
+
+java_test_host {
+    name: "backported_fixes_common_test",
+    srcs: ["tests/java/com/android/build/backportedfixes/common/*.java"],
+    static_libs: [
+        "backported_fixes_common",
+        "backported_fixes_proto",
+        "junit",
+        "truth",
+        "truth-liteproto-extension",
+        "truth-proto-extension",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: ["general-tests"],
+}
+
+java_library {
+    name: "applied_backported_fixes_lib",
+    srcs: ["src/java/com/android/build/backportedfixes/*.java"],
+    static_libs: [
+        "backported_fixes_common",
+        "backported_fixes_proto",
+        "jcommander",
+        "guava",
+    ],
+    host_supported: true,
+}
+
+java_binary_host {
+    name: "applied_backported_fixes_main",
+    main_class: "com.android.build.backportedfixes.Main",
+    static_libs: [
+        "applied_backported_fixes_lib",
+    ],
+}
+
+java_test_host {
+    name: "applied_backported_fixes_test",
+    srcs: ["tests/java/com/android/build/backportedfixes/*.java"],
+    static_libs: [
+        "applied_backported_fixes_lib",
+        "backported_fixes_proto",
+        "junit",
+        "truth",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    test_suites: ["general-tests"],
+}
+
+gensrcs {
+    name: "applied_backported_fix_binpbs",
+    tools: ["aprotoc"],
+    srcs: [
+        "applied_fixes/*.txtpb",
+    ],
+    tool_files: [
+        "backported_fixes.proto",
+    ],
+    output_extension: "binpb",
+    cmd: "$(location aprotoc)  " +
+        " --encode=com.android.build.backportedfixes.BackportedFix" +
+        "  $(location backported_fixes.proto)" +
+        " < $(in)" +
+        " > $(out); echo $(out)",
+}
diff --git a/backported_fixes/OWNERS b/backported_fixes/OWNERS
new file mode 100644
index 0000000..ac176bf
--- /dev/null
+++ b/backported_fixes/OWNERS
@@ -0,0 +1,3 @@
+essick@google.com
+nchalko@google.com
+portmannc@google.com
diff --git a/backported_fixes/applied_fixes/ki350037023.txtpb b/backported_fixes/applied_fixes/ki350037023.txtpb
new file mode 100644
index 0000000..456a7ae
--- /dev/null
+++ b/backported_fixes/applied_fixes/ki350037023.txtpb
@@ -0,0 +1,19 @@
+# Copyright (C) 2024 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.
+#
+# proto-file: ../backported_fixes.proto
+# proto-message: BackportedFix
+
+known_issue: 350037023
+alias: 1
diff --git a/backported_fixes/backported_fixes.proto b/backported_fixes/backported_fixes.proto
new file mode 100644
index 0000000..91618ee
--- /dev/null
+++ b/backported_fixes/backported_fixes.proto
@@ -0,0 +1,37 @@
+// Copyright (C) 2024 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.
+
+syntax = "proto2";
+
+package com.android.build.backportedfixes;
+
+option java_multiple_files = true;
+
+// A list of backported fixes.
+message BackportedFixes {
+  repeated BackportedFix fixes = 1;
+}
+
+// A known issue approved for reporting Build.getBackportedFixStatus
+message BackportedFix {
+
+  // The issue id from the public bug tracker
+  // https://issuetracker.google.com/issues/{known_issue}
+  optional int64 known_issue = 1;
+  // The alias for the known issue.
+  // 1 - 1023 are valid aliases
+  // Must be unique across all backported fixes.
+  optional int32 alias = 2;
+}
+
diff --git a/backported_fixes/src/java/com/android/build/backportedfixes/Main.java b/backported_fixes/src/java/com/android/build/backportedfixes/Main.java
new file mode 100644
index 0000000..79148cc
--- /dev/null
+++ b/backported_fixes/src/java/com/android/build/backportedfixes/Main.java
@@ -0,0 +1,79 @@
+
+/*
+ * Copyright (C) 2024 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.build.backportedfixes;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.build.backportedfixes.common.ClosableCollection;
+import com.android.build.backportedfixes.common.Parser;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.converters.FileConverter;
+import com.google.common.io.Files;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class Main {
+    @Parameter(description = "BackportedFix proto binary files", converter = FileConverter.class,
+            required = true)
+    List<File> fixFiles;
+    @Parameter(description = "The file to write the property value to.",
+            names = {"--property_file", "-p"}, converter = FileConverter.class, required = true)
+    File propertyFile;
+
+    public static void main(String... argv) throws Exception {
+        Main main = new Main();
+        JCommander.newBuilder().addObject(main).build().parse(argv);
+        main.run();
+    }
+
+    Main() {
+    }
+
+    private void run() throws Exception {
+        try (var fixStreams = ClosableCollection.wrap(Parser.getFileInputStreams(fixFiles));
+             var out = Files.newWriter(propertyFile, UTF_8)) {
+            var fixes = Parser.parseBackportedFixes(fixStreams.getCollection());
+            writeFixesAsAliasBitSet(fixes, out);
+        }
+    }
+
+    static void writeFixesAsAliasBitSet(BackportedFixes fixes, Writer out) {
+        PrintWriter printWriter = new PrintWriter(out);
+        printWriter.println("# The following backported fixes have been applied");
+        for (var f : fixes.getFixesList()) {
+            printWriter.printf("# https://issuetracker.google.com/issues/%d with alias %d",
+                    f.getKnownIssue(), f.getAlias());
+            printWriter.println();
+        }
+        var bsArray = Parser.getBitSetArray(
+                fixes.getFixesList().stream().mapToInt(BackportedFix::getAlias).toArray());
+        String bsString = Arrays.stream(bsArray).mapToObj(Long::toString).collect(
+                Collectors.joining(","));
+        printWriter.printf("ro.build.backported_fixes.alias_bitset.long_list=%s", bsString);
+        printWriter.println();
+        if (printWriter.checkError()) {
+            throw new RuntimeException("There was an error writing to " + out.toString());
+        }
+    }
+}
diff --git a/backported_fixes/src/java/com/android/build/backportedfixes/common/ClosableCollection.java b/backported_fixes/src/java/com/android/build/backportedfixes/common/ClosableCollection.java
new file mode 100644
index 0000000..75b6730
--- /dev/null
+++ b/backported_fixes/src/java/com/android/build/backportedfixes/common/ClosableCollection.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2024 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.build.backportedfixes.common;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/** An AutoCloseable holder for a collection of AutoCloseables. */
+public final class ClosableCollection<T extends AutoCloseable, C extends Collection<T>> implements
+        AutoCloseable {
+    C source;
+
+    /** Makes the collection AutoCloseable. */
+    public static <T extends AutoCloseable, C extends Collection<T>> ClosableCollection<T, C> wrap(
+            C source) {
+        return new ClosableCollection<>(source);
+    }
+
+    private ClosableCollection(C source) {
+        this.source = source;
+    }
+
+    /** Get the source collection. */
+    public C getCollection() {
+        return source;
+    }
+
+    /**
+     * Closes each item in the collection.
+     *
+     * @throws Exception if any close throws an an exception, a new exception is thrown with
+     *                   all the exceptions thrown closing the streams added as a suppressed
+     *                   exceptions.
+     */
+    @Override
+    public void close() throws Exception {
+        var failures = new ArrayList<Exception>();
+        for (T t : source) {
+            try {
+                t.close();
+            } catch (Exception e) {
+                failures.add(e);
+            }
+        }
+        if (!failures.isEmpty()) {
+            Exception e = new Exception(
+                    "%d of %d failed while closing".formatted(failures.size(), source.size()));
+            failures.forEach(e::addSuppressed);
+            throw e;
+        }
+    }
+}
diff --git a/backported_fixes/src/java/com/android/build/backportedfixes/common/Parser.java b/backported_fixes/src/java/com/android/build/backportedfixes/common/Parser.java
new file mode 100644
index 0000000..6b08b8f
--- /dev/null
+++ b/backported_fixes/src/java/com/android/build/backportedfixes/common/Parser.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2024 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.build.backportedfixes.common;
+
+import com.android.build.backportedfixes.BackportedFix;
+import com.android.build.backportedfixes.BackportedFixes;
+
+import com.google.common.collect.ImmutableList;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.BitSet;
+import java.util.List;
+
+
+/** Static utilities for working with {@link BackportedFixes}. */
+public final class Parser {
+
+    /** Creates list of FileInputStreams for a list of files. */
+    public static ImmutableList<FileInputStream> getFileInputStreams(List<File> fixFiles) throws
+            FileNotFoundException {
+        var streams = ImmutableList.<FileInputStream>builder();
+        for (var f : fixFiles) {
+            streams.add(new FileInputStream(f));
+        }
+        return streams.build();
+    }
+
+    /** Converts a list of backported fix aliases into a long array representing a {@link BitSet} */
+    public static long[] getBitSetArray(int[] aliases) {
+        BitSet bs = new BitSet();
+        for (int a : aliases) {
+            bs.set(a);
+        }
+        return bs.toLongArray();
+    }
+
+    /**
+     * Creates a {@link BackportedFixes} from a list of {@link BackportedFix} binary proto streams.
+     */
+    public static BackportedFixes parseBackportedFixes(List<? extends InputStream> fixStreams)
+            throws
+            IOException {
+        var fixes = BackportedFixes.newBuilder();
+        for (var s : fixStreams) {
+            BackportedFix fix = BackportedFix.parseFrom(s);
+            fixes.addFixes(fix);
+            s.close();
+        }
+        return fixes.build();
+    }
+
+    private Parser() {
+    }
+}
diff --git a/backported_fixes/tests/java/com/android/build/backportedfixes/MainTest.java b/backported_fixes/tests/java/com/android/build/backportedfixes/MainTest.java
new file mode 100644
index 0000000..84061e1
--- /dev/null
+++ b/backported_fixes/tests/java/com/android/build/backportedfixes/MainTest.java
@@ -0,0 +1,64 @@
+
+/*
+ * Copyright (C) 2024 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.build.backportedfixes;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/** Tests for {@link Main}. */
+public class MainTest {
+
+
+    @Test
+    public void writeFixesAsAliasBitSet_default() {
+        BackportedFixes fixes = BackportedFixes.newBuilder().build();
+        var result = new StringWriter();
+
+        Main.writeFixesAsAliasBitSet(fixes, new PrintWriter(result));
+
+        Truth.assertThat(result.toString())
+                .isEqualTo("""
+                        # The following backported fixes have been applied
+                        ro.build.backported_fixes.alias_bitset.long_list=
+                        """);
+    }
+
+    @Test
+    public void writeFixesAsAliasBitSet_some() {
+        BackportedFixes fixes = BackportedFixes.newBuilder()
+                .addFixes(BackportedFix.newBuilder().setKnownIssue(1234L).setAlias(1))
+                .addFixes(BackportedFix.newBuilder().setKnownIssue(3L).setAlias(65))
+                .addFixes(BackportedFix.newBuilder().setKnownIssue(4L).setAlias(67))
+                .build();
+        var result = new StringWriter();
+
+        Main.writeFixesAsAliasBitSet(fixes, new PrintWriter(result));
+
+        Truth.assertThat(result.toString())
+                .isEqualTo("""
+                        # The following backported fixes have been applied
+                        # https://issuetracker.google.com/issues/1234 with alias 1
+                        # https://issuetracker.google.com/issues/3 with alias 65
+                        # https://issuetracker.google.com/issues/4 with alias 67
+                        ro.build.backported_fixes.alias_bitset.long_list=2,10
+                        """);
+    }
+}
diff --git a/backported_fixes/tests/java/com/android/build/backportedfixes/common/CloseableCollectionTest.java b/backported_fixes/tests/java/com/android/build/backportedfixes/common/CloseableCollectionTest.java
new file mode 100644
index 0000000..d3d84a8
--- /dev/null
+++ b/backported_fixes/tests/java/com/android/build/backportedfixes/common/CloseableCollectionTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.build.backportedfixes.common;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.Truth;
+
+import org.junit.Test;
+
+/** Tests for {@link ClosableCollection}. */
+public class CloseableCollectionTest {
+
+    private static class FakeCloseable implements AutoCloseable {
+        private final boolean throwOnClose;
+        private final String name;
+
+
+        private boolean isClosed = false;
+
+        private FakeCloseable(String name, boolean throwOnClose) {
+            this.name = name;
+            this.throwOnClose = throwOnClose;
+
+        }
+
+        private static FakeCloseable named(String name) {
+            return new FakeCloseable(name, false);
+        }
+
+        private static FakeCloseable failing(String name) {
+            return new FakeCloseable(name, true);
+        }
+
+        public boolean isClosed() {
+            return isClosed;
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (throwOnClose) {
+                throw new Exception(name + " close failed");
+            }
+            isClosed = true;
+        }
+    }
+
+
+    @Test
+    public void bothClosed() throws Exception {
+        var c = ImmutableSet.of(FakeCloseable.named("foo"), FakeCloseable.named("bar"));
+        try (var cc = ClosableCollection.wrap(c);) {
+            Truth.assertThat(cc.getCollection()).isSameInstanceAs(c);
+        }
+        Truth.assertThat(c)
+                .comparingElementsUsing(
+                        Correspondence.transforming(FakeCloseable::isClosed, "is closed"))
+                .containsExactly(true, true);
+    }
+
+    @Test
+    public void bothFailed() {
+        var c = ImmutableSet.of(FakeCloseable.failing("foo"), FakeCloseable.failing("bar"));
+
+        try {
+            try (var cc = ClosableCollection.wrap(c);) {
+                Truth.assertThat(cc.getCollection()).isSameInstanceAs(c);
+            }
+        } catch (Exception e) {
+            Truth.assertThat(e).hasMessageThat().isEqualTo("2 of 2 failed while closing");
+            Truth.assertThat(e.getSuppressed())
+                    .asList()
+                    .comparingElementsUsing(
+                            Correspondence.transforming(Exception::getMessage, "has a message of "))
+                    .containsExactly("foo close failed", "bar close failed");
+        }
+    }
+}
diff --git a/backported_fixes/tests/java/com/android/build/backportedfixes/common/ParserTest.java b/backported_fixes/tests/java/com/android/build/backportedfixes/common/ParserTest.java
new file mode 100644
index 0000000..444e694
--- /dev/null
+++ b/backported_fixes/tests/java/com/android/build/backportedfixes/common/ParserTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2024 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.build.backportedfixes.common;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
+
+import com.android.build.backportedfixes.BackportedFix;
+import com.android.build.backportedfixes.BackportedFixes;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/** Tests for {@link Parser}.*/
+public class ParserTest {
+
+    @Test
+    public void getFileInputStreams() throws IOException {
+        var results = Parser.getFileInputStreams(
+                ImmutableList.of(Files.createTempFile("test", null).toFile()));
+        assertThat(results).isNotEmpty();
+    }
+
+
+    @Test
+    public void getBitSetArray_empty() {
+        var results = Parser.getBitSetArray(new int[]{});
+        assertThat(results).isEmpty();
+    }
+
+    @Test
+    public void getBitSetArray_2_3_64() {
+        var results = Parser.getBitSetArray(new int[]{2,3,64});
+        assertThat(results).asList().containsExactly(12L,1L).inOrder();
+    }
+
+    @Test
+    public void parseBackportedFixes_empty() throws IOException {
+        var result = Parser.parseBackportedFixes(ImmutableList.of());
+        assertThat(result).isEqualTo(BackportedFixes.getDefaultInstance());
+    }
+
+    @Test
+    public void parseBackportedFixes_oneBlank() throws IOException {
+        var result = Parser.parseBackportedFixes(
+                ImmutableList.of(inputStream(BackportedFix.getDefaultInstance())));
+
+        assertThat(result).isEqualTo(
+                BackportedFixes.newBuilder()
+                        .addFixes(BackportedFix.getDefaultInstance())
+                        .build());
+    }
+
+    @Test
+    public void parseBackportedFixes_two() throws IOException {
+        BackportedFix ki123 = BackportedFix.newBuilder()
+                .setKnownIssue(123)
+                .setAlias(1)
+                .build();
+        BackportedFix ki456 = BackportedFix.newBuilder()
+                .setKnownIssue(456)
+                .setAlias(2)
+                .build();
+        var result = Parser.parseBackportedFixes(
+                ImmutableList.of(inputStream(ki123), inputStream(ki456)));
+        assertThat(result).isEqualTo(
+                BackportedFixes.newBuilder()
+                        .addFixes(ki123)
+                        .addFixes(ki456)
+                        .build());
+    }
+
+    private static ByteArrayInputStream inputStream(BackportedFix f) {
+        return new ByteArrayInputStream(f.toByteArray());
+    }
+}
diff --git a/ci/Android.bp b/ci/Android.bp
index 2407c51..3f28be4 100644
--- a/ci/Android.bp
+++ b/ci/Android.bp
@@ -103,13 +103,10 @@
         "test_mapping_module_retriever.py",
         "build_context.py",
         "test_discovery_agent.py",
+        "metrics_agent.py",
+        "buildbot.py",
     ],
     main: "build_test_suites.py",
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
     libs: [
         "soong-metrics-proto-py",
     ],
@@ -123,6 +120,11 @@
         "test_mapping_module_retriever.py",
         "build_context.py",
         "test_discovery_agent.py",
+        "metrics_agent.py",
+        "buildbot.py",
+    ],
+    libs: [
+        "soong-metrics-proto-py",
     ],
 }
 
diff --git a/ci/build_context.py b/ci/build_context.py
index cc48d53..c7a1def 100644
--- a/ci/build_context.py
+++ b/ci/build_context.py
@@ -47,6 +47,9 @@
       self.is_test_mapping = False
       self.test_mapping_test_groups = set()
       self.file_download_options = set()
+      self.name = test_info_dict.get('name')
+      self.command = test_info_dict.get('command')
+      self.extra_options = test_info_dict.get('extraOptions')
       for opt in test_info_dict.get('extraOptions', []):
         key = opt.get('key')
         if key == 'test-mapping-test-group':
diff --git a/ci/build_test_suites b/ci/build_test_suites
index 5aaf2f4..9d11268 100755
--- a/ci/build_test_suites
+++ b/ci/build_test_suites
@@ -1,4 +1,4 @@
-#!prebuilts/build-tools/linux-x86/bin/py3-cmd -B
+#!/usr/bin/env bash
 #
 # Copyright 2024, The Android Open Source Project
 #
@@ -14,7 +14,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import build_test_suites
-import sys
-
-build_test_suites.main(sys.argv[1:])
+build/soong/soong_ui.bash --make-mode build_test_suites || exit $?
+$(build/soong/soong_ui.bash --dumpvar-mode HOST_OUT)/bin/build_test_suites $@ || exit $?
diff --git a/ci/build_test_suites.py b/ci/build_test_suites.py
index 9e56a20..cd9d76d 100644
--- a/ci/build_test_suites.py
+++ b/ci/build_test_suites.py
@@ -20,11 +20,14 @@
 import logging
 import os
 import pathlib
+import re
 import subprocess
 import sys
 from typing import Callable
 from build_context import BuildContext
 import optimized_targets
+import metrics_agent
+import test_discovery_agent
 
 
 REQUIRED_ENV_VARS = frozenset(['TARGET_PRODUCT', 'TARGET_RELEASE', 'TOP'])
@@ -70,7 +73,24 @@
 
     build_targets = set()
     packaging_commands_getters = []
+    test_discovery_zip_regexes = set()
+    optimization_rationale = ''
+    try:
+      # Do not use these regexes for now, only run this to collect data on what
+      # would be optimized.
+      test_discovery_zip_regexes = self._get_test_discovery_zip_regexes()
+      logging.info(f'Discovered test discovery regexes: {test_discovery_zip_regexes}')
+    except test_discovery_agent.TestDiscoveryError as e:
+      optimization_rationale = e.message
+      logging.warning(f'Unable to perform test discovery: {optimization_rationale}')
     for target in self.args.extra_targets:
+      if optimization_rationale:
+        get_metrics_agent().report_unoptimized_target(target, optimization_rationale)
+      else:
+        regex = r'\b(%s)\b' % re.escape(target)
+        if any(re.search(regex, opt) for opt in test_discovery_zip_regexes):
+          get_metrics_agent().report_optimized_target(target)
+
       if self._unused_target_exclusion_enabled(
           target
       ) and not self.build_context.build_target_used(target):
@@ -97,6 +117,34 @@
         in self.build_context.enabled_build_features
     )
 
+  def _get_test_discovery_zip_regexes(self) -> set[str]:
+    build_target_regexes = set()
+    for test_info in self.build_context.test_infos:
+      tf_command = self._build_tf_command(test_info)
+      discovery_agent = test_discovery_agent.TestDiscoveryAgent(tradefed_args=tf_command)
+      for regex in discovery_agent.discover_test_zip_regexes():
+        build_target_regexes.add(regex)
+    return build_target_regexes
+
+
+  def _build_tf_command(self, test_info) -> list[str]:
+    command = [test_info.command]
+    for extra_option in test_info.extra_options:
+      if not extra_option.get('key'):
+        continue
+      arg_key = '--' + extra_option.get('key')
+      if arg_key == '--build-id':
+        command.append(arg_key)
+        command.append(os.environ.get('BUILD_NUMBER'))
+        continue
+      if extra_option.get('values'):
+        for value in extra_option.get('values'):
+          command.append(arg_key)
+          command.append(value)
+      else:
+        command.append(arg_key)
+
+    return command
 
 @dataclass(frozen=True)
 class BuildPlan:
@@ -113,19 +161,27 @@
   Returns:
     The exit code of the build.
   """
-  args = parse_args(argv)
-  check_required_env()
-  build_context = BuildContext(load_build_context())
-  build_planner = BuildPlanner(
-      build_context, args, optimized_targets.OPTIMIZED_BUILD_TARGETS
-  )
-  build_plan = build_planner.create_build_plan()
+  get_metrics_agent().analysis_start()
+  try:
+    args = parse_args(argv)
+    check_required_env()
+    build_context = BuildContext(load_build_context())
+    build_planner = BuildPlanner(
+        build_context, args, optimized_targets.OPTIMIZED_BUILD_TARGETS
+    )
+    build_plan = build_planner.create_build_plan()
+  except:
+    raise
+  finally:
+    get_metrics_agent().analysis_end()
 
   try:
     execute_build_plan(build_plan)
   except BuildFailureError as e:
     logging.error('Build command failed! Check build_log for details.')
     return e.return_code
+  finally:
+    get_metrics_agent().end_reporting()
 
   return 0
 
@@ -183,12 +239,15 @@
   except subprocess.CalledProcessError as e:
     raise BuildFailureError(e.returncode) from e
 
-  for packaging_commands_getter in build_plan.packaging_commands_getters:
-    try:
+  get_metrics_agent().packaging_start()
+  try:
+    for packaging_commands_getter in build_plan.packaging_commands_getters:
       for packaging_command in packaging_commands_getter():
         run_command(packaging_command)
-    except subprocess.CalledProcessError as e:
-      raise BuildFailureError(e.returncode) from e
+  except subprocess.CalledProcessError as e:
+    raise BuildFailureError(e.returncode) from e
+  finally:
+    get_metrics_agent().packaging_end()
 
 
 def get_top() -> pathlib.Path:
@@ -199,6 +258,10 @@
   subprocess.run(args=args, check=True, stdout=stdout)
 
 
+def get_metrics_agent():
+  return metrics_agent.MetricsAgent.instance()
+
+
 def main(argv):
   dist_dir = os.environ.get('DIST_DIR')
   if dist_dir:
diff --git a/ci/build_test_suites_test.py b/ci/build_test_suites_test.py
index 2afaab7..26f4316 100644
--- a/ci/build_test_suites_test.py
+++ b/ci/build_test_suites_test.py
@@ -37,6 +37,8 @@
 import ci_test_lib
 import optimized_targets
 from pyfakefs import fake_filesystem_unittest
+import metrics_agent
+import test_discovery_agent
 
 
 class BuildTestSuitesTest(fake_filesystem_unittest.TestCase):
@@ -52,6 +54,10 @@
     self.addCleanup(subprocess_run_patcher.stop)
     self.mock_subprocess_run = subprocess_run_patcher.start()
 
+    metrics_agent_finalize_patcher = mock.patch('metrics_agent.MetricsAgent.end_reporting')
+    self.addCleanup(metrics_agent_finalize_patcher.stop)
+    self.mock_metrics_agent_end = metrics_agent_finalize_patcher.start()
+
     self._setup_working_build_env()
 
   def test_missing_target_release_env_var_raises(self):
@@ -256,6 +262,12 @@
     def get_enabled_flag(self):
       return f'{self.target}_enabled'
 
+  def setUp(self):
+    test_discovery_agent_patcher = mock.patch('test_discovery_agent.TestDiscoveryAgent.discover_test_zip_regexes')
+    self.addCleanup(test_discovery_agent_patcher.stop)
+    self.mock_test_discovery_agent_end = test_discovery_agent_patcher.start()
+
+
   def test_build_optimization_off_builds_everything(self):
     build_targets = {'target_1', 'target_2'}
     build_planner = self.create_build_planner(
diff --git a/ci/metrics_agent.py b/ci/metrics_agent.py
new file mode 100644
index 0000000..bc2479e
--- /dev/null
+++ b/ci/metrics_agent.py
@@ -0,0 +1,116 @@
+# Copyright 2024, 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.
+
+"""MetricsAgent is a singleton class that collects metrics for optimized build."""
+
+from enum import Enum
+import time
+import metrics_pb2
+import os
+import logging
+
+
+class MetricsAgent:
+  _SOONG_METRICS_PATH = 'logs/soong_metrics'
+  _DIST_DIR = 'DIST_DIR'
+  _instance = None
+
+  def __init__(self):
+    raise RuntimeError(
+        'MetricsAgent cannot be instantialized, use instance() instead'
+    )
+
+  @classmethod
+  def instance(cls):
+    if not cls._instance:
+      cls._instance = cls.__new__(cls)
+      cls._instance._proto = metrics_pb2.OptimizedBuildMetrics()
+      cls._instance._init_proto()
+      cls._instance._target_results = dict()
+
+    return cls._instance
+
+  def _init_proto(self):
+    self._proto.analysis_perf.name = 'Optimized build analysis time.'
+    self._proto.packaging_perf.name = 'Optimized build total packaging time.'
+
+  def analysis_start(self):
+    self._proto.analysis_perf.start_time = time.time_ns()
+
+  def analysis_end(self):
+    self._proto.analysis_perf.real_time = (
+        time.time_ns() - self._proto.analysis_perf.start_time
+    )
+
+  def packaging_start(self):
+    self._proto.packaging_perf.start_time = time.time_ns()
+
+  def packaging_end(self):
+    self._proto.packaging_perf.real_time = (
+        time.time_ns() - self._proto.packaging_perf.start_time
+    )
+
+  def report_optimized_target(self, name: str):
+    target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult()
+    target_result.name = name
+    target_result.optimized = True
+    self._target_results[name] = target_result
+
+  def report_unoptimized_target(self, name: str, optimization_rationale: str):
+    target_result = metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult()
+    target_result.name = name
+    target_result.optimization_rationale = optimization_rationale
+    target_result.optimized = False
+    self._target_results[name] = target_result
+
+  def target_packaging_start(self, name: str):
+    target_result = self._target_results.get(name)
+    target_result.packaging_perf.start_time = time.time_ns()
+    self._target_results[name] = target_result
+
+  def target_packaging_end(self, name: str):
+    target_result = self._target_results.get(name)
+    target_result.packaging_perf.real_time = (
+        time.time_ns() - target_result.packaging_perf.start_time
+    )
+
+  def add_target_artifact(
+      self,
+      target_name: str,
+      artifact_name: str,
+      size: int,
+      included_modules: set[str],
+  ):
+    target_result = self.target_results.get(target_name)
+    artifact = (
+        metrics_pb2.OptimizedBuildMetrics.TargetOptimizationResult.OutputArtifact()
+    )
+    artifact.name = artifact_name
+    artifact.size = size
+    for module in included_modules:
+      artifact.included_modules.add(module)
+    target_result.output_artifacts.add(artifact)
+
+  def end_reporting(self):
+    for target_result in self._target_results.values():
+      self._proto.target_result.append(target_result)
+    soong_metrics_proto = metrics_pb2.MetricsBase()
+    # Read in existing metrics that should have been written out by the soong
+    # build command so that we don't overwrite them.
+    with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'rb') as f:
+      soong_metrics_proto.ParseFromString(f.read())
+    soong_metrics_proto.optimized_build_metrics.CopyFrom(self._proto)
+    logging.info(soong_metrics_proto)
+    with open(os.path.join(os.environ[self._DIST_DIR], self._SOONG_METRICS_PATH), 'wb') as f:
+      f.write(soong_metrics_proto.SerializeToString())
diff --git a/ci/test_discovery_agent.py b/ci/test_discovery_agent.py
index 4eed28d..008ee47 100644
--- a/ci/test_discovery_agent.py
+++ b/ci/test_discovery_agent.py
@@ -17,7 +17,6 @@
 import logging
 import os
 import subprocess
-import buildbot
 
 
 class TestDiscoveryAgent:
@@ -50,7 +49,7 @@
       A list of test zip regexes that TF is going to try to pull files from.
     """
     test_discovery_output_file_name = os.path.join(
-        buildbot.OutDir(), self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
+        os.environ.get('TOP'), 'out', self._TRADEFED_DISCOVERY_OUTPUT_FILE_NAME
     )
     with open(
         test_discovery_output_file_name, mode="w+t"
diff --git a/core/layoutlib_data.mk b/core/layoutlib_data.mk
index 06735df..dabcfb2 100644
--- a/core/layoutlib_data.mk
+++ b/core/layoutlib_data.mk
@@ -106,6 +106,7 @@
 _layoutlib_font_config_files := $(sort $(wildcard frameworks/base/data/fonts/*.xml))
 _layoutlib_fonts_files := $(filter $(TARGET_OUT)/fonts/%.ttf $(TARGET_OUT)/fonts/%.ttc $(TARGET_OUT)/fonts/%.otf, $(INTERNAL_SYSTEMIMAGE_FILES))
 _layoutlib_keyboard_files := $(sort $(wildcard frameworks/base/data/keyboards/*.kcm))
+_layoutlib_hyphen_files := $(filter $(TARGET_OUT)/usr/hyphen-data/%.hyb, $(INTERNAL_SYSTEMIMAGE_FILES))
 
 # Find out files disted with layoutlib in Soong.
 ### Filter out static libraries for Windows and files already handled in make.
@@ -135,6 +136,13 @@
 	  echo data/keyboards/$(notdir $f),frameworks/base/data/keyboards,prebuilt_etc,,,,,$f,,, >> $@; \
 	)
 
+	$(foreach f,$(_layoutlib_hyphen_files), \
+	  $(eval _module_name := $(ALL_INSTALLED_FILES.$f)) \
+	  $(eval _module_path := $(strip $(sort $(ALL_MODULES.$(_module_name).PATH)))) \
+	  $(eval _soong_module_type := $(strip $(sort $(ALL_MODULES.$(_module_name).SOONG_MODULE_TYPE)))) \
+	  echo data/hyphen-data/$(notdir $f),$(_module_path),$(_soong_module_type),,,,,$f,,, >> $@; \
+	)
+
 	$(foreach f,$(_layoutlib_files_disted_by_soong), \
 	  $(eval _prebuilt_module_file := $(call word-colon,1,$f)) \
 	  $(eval _dist_file := $(call word-colon,2,$f)) \
@@ -163,7 +171,7 @@
 
 .PHONY: layoutlib-sbom
 layoutlib-sbom: $(LAYOUTLIB_SBOM)/layoutlib.spdx.json
-$(LAYOUTLIB_SBOM)/layoutlib.spdx.json: $(PRODUCT_OUT)/always_dirty_file.txt $(GEN_SBOM) $(LAYOUTLIB_SBOM)/sbom-metadata.csv $(_layoutlib_font_config_files) $(_layoutlib_fonts_files) $(LAYOUTLIB_BUILD_PROP)/layoutlib-build.prop $(_layoutlib_keyboard_files) $(LAYOUTLIB_RES_FILES) $(EMULATED_OVERLAYS_FILES) $(DEVICE_OVERLAYS_FILES)
+$(LAYOUTLIB_SBOM)/layoutlib.spdx.json: $(PRODUCT_OUT)/always_dirty_file.txt $(GEN_SBOM) $(LAYOUTLIB_SBOM)/sbom-metadata.csv $(_layoutlib_font_config_files) $(_layoutlib_fonts_files) $(LAYOUTLIB_BUILD_PROP)/layoutlib-build.prop $(_layoutlib_keyboard_files) $(_layoutlib_hyphen_files) $(LAYOUTLIB_RES_FILES) $(EMULATED_OVERLAYS_FILES) $(DEVICE_OVERLAYS_FILES)
 	rm -rf $@
 	$(GEN_SBOM) --output_file $@ --metadata $(LAYOUTLIB_SBOM)/sbom-metadata.csv --build_version $(BUILD_FINGERPRINT_FROM_FILE) --product_mfr "$(PRODUCT_MANUFACTURER)" --module_name "layoutlib" --json
 
diff --git a/core/main.mk b/core/main.mk
index f96cf04..624df49 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -1399,6 +1399,7 @@
     $(INSTALLED_RAMDISK_TARGET) \
     $(INSTALLED_BOOTIMAGE_TARGET) \
     $(INSTALLED_INIT_BOOT_IMAGE_TARGET) \
+    $(INSTALLED_DTBOIMAGE_TARGET) \
     $(INSTALLED_RADIOIMAGE_TARGET) \
     $(INSTALLED_DEBUG_RAMDISK_TARGET) \
     $(INSTALLED_DEBUG_BOOTIMAGE_TARGET) \
diff --git a/core/soong_config.mk b/core/soong_config.mk
index ee6a9f6..0f82b68 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -397,6 +397,10 @@
     $(call add_json_str, BoardSquashfsCompressor, $(BOARD_$(image_type)IMAGE_SQUASHFS_COMPRESSOR)) \
     $(call add_json_str, BoardSquashfsCompressorOpt, $(BOARD_$(image_type)IMAGE_SQUASHFS_COMPRESSOR_OPT)) \
     $(call add_json_str, BoardSquashfsDisable4kAlign, $(BOARD_$(image_type)IMAGE_SQUASHFS_DISABLE_4K_ALIGN)) \
+    $(call add_json_str, BoardAvbKeyPath, $(BOARD_AVB_$(image_type)_KEY_PATH)) \
+    $(call add_json_str, BoardAvbAlgorithm, $(BOARD_AVB_$(image_type)_ALGORITHM)) \
+    $(call add_json_str, BoardAvbRollbackIndex, $(BOARD_AVB_$(image_type)_ROLLBACK_INDEX)) \
+    $(call add_json_str, BoardAvbRollbackIndexLocation, $(BOARD_AVB_$(image_type)_ROLLBACK_INDEX_LOCATION)) \
     $(call add_json_str, ProductBaseFsPath, $(PRODUCT_$(image_type)_BASE_FS_PATH)) \
     $(call add_json_str, ProductHeadroom, $(PRODUCT_$(image_type)_HEADROOM)) \
     $(call add_json_str, ProductVerityPartition, $(PRODUCT_$(image_type)_VERITY_PARTITION)) \
diff --git a/core/tasks/general-tests-shared-libs.mk b/core/tasks/general-tests-shared-libs.mk
deleted file mode 100644
index 2405140..0000000
--- a/core/tasks/general-tests-shared-libs.mk
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (C) 2024 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.
-
-.PHONY: general-tests-shared-libs
-
-intermediates_dir := $(call intermediates-dir-for,PACKAGING,general-tests-shared-libs)
-
-general_tests_shared_libs_zip := $(PRODUCT_OUT)/general-tests_host-shared-libs.zip
-
-# Filter shared entries between general-tests and device-tests's HOST_SHARED_LIBRARY.FILES,
-# to avoid warning about overriding commands.
-my_host_shared_lib_for_general_tests := \
-  $(foreach m,$(filter $(COMPATIBILITY.device-tests.HOST_SHARED_LIBRARY.FILES),\
-	   $(COMPATIBILITY.general-tests.HOST_SHARED_LIBRARY.FILES)),$(call word-colon,2,$(m)))
-my_general_tests_shared_lib_files := \
-  $(filter-out $(COMPATIBILITY.device-tests.HOST_SHARED_LIBRARY.FILES),\
-	 $(COMPATIBILITY.general-tests.HOST_SHARED_LIBRARY.FILES))
-
-my_host_shared_lib_for_general_tests += $(call copy-many-files,$(my_general_tests_shared_lib_files))
-
-$(general_tests_shared_libs_zip) : PRIVATE_INTERMEDIATES_DIR := $(intermediates_dir)
-$(general_tests_shared_libs_zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_general_tests)
-$(general_tests_shared_libs_zip) : PRIVATE_general_host_shared_libs_zip := $(general_tests_shared_libs_zip)
-$(general_tests_shared_libs_zip) : $(my_host_shared_lib_for_general_tests) $(SOONG_ZIP)
-	rm -rf $(PRIVATE_INTERMEDIATES_DIR)
-	mkdir -p $(PRIVATE_INTERMEDIATES_DIR) $(PRIVATE_INTERMEDIATES_DIR)/tools
-	$(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
-	  echo $$shared_lib >> $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list; \
-	done
-	grep $(HOST_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list > $(PRIVATE_INTERMEDIATES_DIR)/host-shared-libs.list || true
-	$(SOONG_ZIP) -d -o $(PRIVATE_general_host_shared_libs_zip) \
-	  -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host-shared-libs.list
-
-general-tests-shared-libs: $(general_tests_shared_libs_zip)
-$(call dist-for-goals, general-tests-shared-libs, $(general_tests_shared_libs_zip))
-
-$(call declare-1p-container,$(general_tests_shared_libs_zip),)
-$(call declare-container-license-deps,$(general_tests_shared_libs_zip),$(my_host_shared_lib_for_general_tests),$(PRODUCT_OUT)/:/)
-
-intermediates_dir :=
-general_tests_shared_libs_zip :=
diff --git a/core/tasks/general-tests.mk b/core/tasks/general-tests.mk
index d6fc072..37b16ad 100644
--- a/core/tasks/general-tests.mk
+++ b/core/tasks/general-tests.mk
@@ -37,22 +37,64 @@
 .PHONY: vts_kernel_ltp_tests
 vts_kernel_ltp_tests: $(copy_ltp_tests)
 
-general_tests_shared_libs_zip := $(PRODUCT_OUT)/general-tests_host-shared-libs.zip
+# Filter shared entries between general-tests and device-tests's HOST_SHARED_LIBRARY.FILES,
+# to avoid warning about overriding commands.
+my_host_shared_lib_for_general_tests := \
+  $(foreach m,$(filter $(COMPATIBILITY.device-tests.HOST_SHARED_LIBRARY.FILES),\
+	   $(COMPATIBILITY.general-tests.HOST_SHARED_LIBRARY.FILES)),$(call word-colon,2,$(m)))
+my_general_tests_shared_lib_files := \
+  $(filter-out $(COMPATIBILITY.device-tests.HOST_SHARED_LIBRARY.FILES),\
+	 $(COMPATIBILITY.general-tests.HOST_SHARED_LIBRARY.FILES))
 
-$(general_tests_zip) : $(general_tests_shared_libs_zip)
+my_host_shared_lib_for_general_tests += $(call copy-many-files,$(my_general_tests_shared_lib_files))
+
+my_host_shared_lib_symlinks := \
+    $(filter $(COMPATIBILITY.host-unit-tests.SYMLINKS),\
+	$(COMPATIBILITY.general-tests.SYMLINKS))
+
+my_general_tests_symlinks := \
+    $(filter-out $(COMPATIBILITY.camera-hal-tests.SYMLINKS),\
+    $(filter-out $(COMPATIBILITY.host-unit-tests.SYMLINKS),\
+	 $(COMPATIBILITY.general-tests.SYMLINKS)))
+
+my_symlinks_for_general_tests := $(foreach f,$(my_general_tests_symlinks),\
+	$(strip $(eval _cmf_tuple := $(subst :, ,$(f))) \
+	$(eval _cmf_dep := $(word 1,$(_cmf_tuple))) \
+	$(eval _cmf_src := $(word 2,$(_cmf_tuple))) \
+	$(eval _cmf_dest := $(word 3,$(_cmf_tuple))) \
+	$(call symlink-file,$(_cmf_dep),$(_cmf_src),$(_cmf_dest)) \
+	$(_cmf_dest)))
+
+# In this one directly take the overlap into the zip since we can't rewrite rules
+my_symlinks_for_general_tests += $(foreach f,$(my_host_shared_lib_symlinks),\
+        $(strip $(eval _cmf_tuple := $(subst :, ,$(f))) \
+        $(eval _cmf_dep := $(word 1,$(_cmf_tuple))) \
+        $(eval _cmf_src := $(word 2,$(_cmf_tuple))) \
+        $(eval _cmf_dest := $(word 3,$(_cmf_tuple))) \
+        $(_cmf_dest)))
+
 $(general_tests_zip) : $(copy_ltp_tests)
 $(general_tests_zip) : PRIVATE_KERNEL_LTP_HOST_OUT := $(kernel_ltp_host_out)
 $(general_tests_zip) : PRIVATE_general_tests_list_zip := $(general_tests_list_zip)
 $(general_tests_zip) : .KATI_IMPLICIT_OUTPUTS := $(general_tests_list_zip) $(general_tests_configs_zip)
 $(general_tests_zip) : PRIVATE_TOOLS := $(general_tests_tools)
 $(general_tests_zip) : PRIVATE_INTERMEDIATES_DIR := $(intermediates_dir)
+$(general_tests_zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_general_tests)
+$(general_tests_zip) : PRIVATE_SYMLINKS := $(my_symlinks_for_general_tests)
 $(general_tests_zip) : PRIVATE_general_tests_configs_zip := $(general_tests_configs_zip)
-$(general_tests_zip) : $(COMPATIBILITY.general-tests.FILES) $(COMPATIBILITY.general-tests.SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES) $(general_tests_tools) $(SOONG_ZIP)
+$(general_tests_zip) : $(COMPATIBILITY.general-tests.FILES) $(my_host_shared_lib_for_general_tests) $(COMPATIBILITY.general-tests.SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES) $(general_tests_tools) $(my_symlinks_for_general_tests) $(SOONG_ZIP)
 	rm -rf $(PRIVATE_INTERMEDIATES_DIR)
 	rm -f $@ $(PRIVATE_general_tests_list_zip)
 	mkdir -p $(PRIVATE_INTERMEDIATES_DIR) $(PRIVATE_INTERMEDIATES_DIR)/tools
 	echo $(sort $(COMPATIBILITY.general-tests.FILES) $(COMPATIBILITY.general-tests.SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES)) | tr " " "\n" > $(PRIVATE_INTERMEDIATES_DIR)/list
 	find $(PRIVATE_KERNEL_LTP_HOST_OUT) >> $(PRIVATE_INTERMEDIATES_DIR)/list
+	for symlink in $(PRIVATE_SYMLINKS); do \
+	  echo $$symlink >> $(PRIVATE_INTERMEDIATES_DIR)/list; \
+	done
+	$(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
+	  echo $$shared_lib >> $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list; \
+	done
+	grep $(HOST_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/shared-libs.list > $(PRIVATE_INTERMEDIATES_DIR)/host-shared-libs.list || true
 	grep $(HOST_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/host.list || true
 	grep $(TARGET_OUT_TESTCASES) $(PRIVATE_INTERMEDIATES_DIR)/list > $(PRIVATE_INTERMEDIATES_DIR)/target.list || true
 	grep -e .*\\.config$$ $(PRIVATE_INTERMEDIATES_DIR)/host.list > $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list || true
@@ -62,6 +104,7 @@
 	  -P host -C $(PRIVATE_INTERMEDIATES_DIR) -D $(PRIVATE_INTERMEDIATES_DIR)/tools \
 	  -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host.list \
 	  -P target -C $(PRODUCT_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/target.list \
+	  -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host-shared-libs.list \
 	  -sha256
 	$(SOONG_ZIP) -d -o $(PRIVATE_general_tests_configs_zip) \
 	  -P host -C $(HOST_OUT) -l $(PRIVATE_INTERMEDIATES_DIR)/host-test-configs.list \
@@ -82,3 +125,8 @@
 general_tests_list_zip :=
 general_tests_configs_zip :=
 general_tests_shared_libs_zip :=
+my_host_shared_lib_for_general_tests :=
+my_symlinks_for_general_tests :=
+my_general_tests_shared_lib_files :=
+my_general_tests_symlinks :=
+my_host_shared_lib_symlinks :=
diff --git a/tools/aconfig/aconfig/src/commands.rs b/tools/aconfig/aconfig/src/commands.rs
index c7d4d9c..81119f3 100644
--- a/tools/aconfig/aconfig/src/commands.rs
+++ b/tools/aconfig/aconfig/src/commands.rs
@@ -69,6 +69,7 @@
     declarations: Vec<Input>,
     values: Vec<Input>,
     default_permission: ProtoFlagPermission,
+    allow_read_write: bool,
 ) -> Result<Vec<u8>> {
     let mut parsed_flags = ProtoParsedFlags::new();
 
@@ -195,6 +196,16 @@
         }
     }
 
+    if !allow_read_write {
+        if let Some(pf) = parsed_flags
+            .parsed_flag
+            .iter()
+            .find(|pf| pf.permission() == ProtoFlagPermission::READ_WRITE)
+        {
+            bail!("flag {} has permission READ_WRITE, but allow_read_write is false", pf.name());
+        }
+    }
+
     // Create a sorted parsed_flags
     aconfig_protos::parsed_flags::sort_parsed_flags(&mut parsed_flags);
     aconfig_protos::parsed_flags::verify_fields(&parsed_flags)?;
@@ -576,6 +587,7 @@
             declaration,
             value,
             ProtoFlagPermission::READ_ONLY,
+            true,
         )
         .unwrap();
         let parsed_flags =
@@ -609,6 +621,7 @@
             declaration,
             value,
             ProtoFlagPermission::READ_WRITE,
+            true,
         )
         .unwrap_err();
         assert_eq!(
@@ -640,6 +653,7 @@
             declaration,
             value,
             ProtoFlagPermission::READ_WRITE,
+            true,
         )
         .unwrap_err();
         assert_eq!(
@@ -647,6 +661,121 @@
             "failed to parse memory: expected container argument.container, got declaration.container"
         );
     }
+    #[test]
+    fn test_parse_flags_no_allow_read_write_default_error() {
+        let first_flag = r#"
+        package: "com.first"
+        container: "com.first.container"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of the first flag."
+            bug: "123"
+        }
+        "#;
+        let declaration =
+            vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
+
+        let error = crate::commands::parse_flags(
+            "com.first",
+            Some("com.first.container"),
+            declaration,
+            vec![],
+            ProtoFlagPermission::READ_WRITE,
+            false,
+        )
+        .unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "flag first has permission READ_WRITE, but allow_read_write is false"
+        );
+    }
+
+    #[test]
+    fn test_parse_flags_no_allow_read_write_value_error() {
+        let first_flag = r#"
+        package: "com.first"
+        container: "com.first.container"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of the first flag."
+            bug: "123"
+        }
+        "#;
+        let declaration =
+            vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
+
+        let first_flag_value = r#"
+        flag_value {
+            package: "com.first"
+            name: "first"
+            state: DISABLED
+            permission: READ_WRITE
+        }
+        "#;
+        let value = vec![Input {
+            source: "memory".to_string(),
+            reader: Box::new(first_flag_value.as_bytes()),
+        }];
+        let error = crate::commands::parse_flags(
+            "com.first",
+            Some("com.first.container"),
+            declaration,
+            value,
+            ProtoFlagPermission::READ_ONLY,
+            false,
+        )
+        .unwrap_err();
+        assert_eq!(
+            format!("{:?}", error),
+            "flag first has permission READ_WRITE, but allow_read_write is false"
+        );
+    }
+
+    #[test]
+    fn test_parse_flags_no_allow_read_write_success() {
+        let first_flag = r#"
+        package: "com.first"
+        container: "com.first.container"
+        flag {
+            name: "first"
+            namespace: "first_ns"
+            description: "This is the description of the first flag."
+            bug: "123"
+        }
+        "#;
+        let declaration =
+            vec![Input { source: "memory".to_string(), reader: Box::new(first_flag.as_bytes()) }];
+
+        let first_flag_value = r#"
+        flag_value {
+            package: "com.first"
+            name: "first"
+            state: DISABLED
+            permission: READ_ONLY
+        }
+        "#;
+        let value = vec![Input {
+            source: "memory".to_string(),
+            reader: Box::new(first_flag_value.as_bytes()),
+        }];
+        let flags_bytes = crate::commands::parse_flags(
+            "com.first",
+            Some("com.first.container"),
+            declaration,
+            value,
+            ProtoFlagPermission::READ_ONLY,
+            false,
+        )
+        .unwrap();
+        let parsed_flags =
+            aconfig_protos::parsed_flags::try_from_binary_proto(&flags_bytes).unwrap();
+        assert_eq!(1, parsed_flags.parsed_flag.len());
+        let parsed_flag = parsed_flags.parsed_flag.first().unwrap();
+        assert_eq!(ProtoFlagState::DISABLED, parsed_flag.state());
+        assert_eq!(ProtoFlagPermission::READ_ONLY, parsed_flag.permission());
+    }
 
     #[test]
     fn test_parse_flags_override_fixed_read_only() {
@@ -682,6 +811,7 @@
             declaration,
             value,
             ProtoFlagPermission::READ_WRITE,
+            true,
         )
         .unwrap_err();
         assert_eq!(
@@ -716,6 +846,7 @@
             declaration,
             value,
             ProtoFlagPermission::READ_ONLY,
+            true,
         )
         .unwrap();
         let parsed_flags =
diff --git a/tools/aconfig/aconfig/src/main.rs b/tools/aconfig/aconfig/src/main.rs
index e184efe..c390288 100644
--- a/tools/aconfig/aconfig/src/main.rs
+++ b/tools/aconfig/aconfig/src/main.rs
@@ -62,6 +62,12 @@
                             &commands::DEFAULT_FLAG_PERMISSION,
                         )),
                 )
+                .arg(
+                    Arg::new("allow-read-write")
+                        .long("allow-read-write")
+                        .value_parser(clap::value_parser!(bool))
+                        .default_value("true"),
+                )
                 .arg(Arg::new("cache").long("cache").required(true)),
         )
         .subcommand(
@@ -242,12 +248,15 @@
                 sub_matches,
                 "default-permission",
             )?;
+            let allow_read_write = get_optional_arg::<bool>(sub_matches, "allow-read-write")
+                .expect("failed to parse allow-read-write");
             let output = commands::parse_flags(
                 package,
                 container,
                 declarations,
                 values,
                 *default_permission,
+                *allow_read_write,
             )
             .context("failed to create cache")?;
             let path = get_required_arg::<String>(sub_matches, "cache")?;
diff --git a/tools/aconfig/aconfig/src/storage/mod.rs b/tools/aconfig/aconfig/src/storage/mod.rs
index 14a0957..ef1b4f6 100644
--- a/tools/aconfig/aconfig/src/storage/mod.rs
+++ b/tools/aconfig/aconfig/src/storage/mod.rs
@@ -171,6 +171,7 @@
                         reader: Box::new(value_content),
                     }],
                     crate::commands::DEFAULT_FLAG_PERMISSION,
+                    true,
                 )
                 .unwrap();
                 aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
diff --git a/tools/aconfig/aconfig/src/test.rs b/tools/aconfig/aconfig/src/test.rs
index a19b372..10da252 100644
--- a/tools/aconfig/aconfig/src/test.rs
+++ b/tools/aconfig/aconfig/src/test.rs
@@ -266,6 +266,7 @@
                 reader: Box::new(include_bytes!("../tests/read_only_test.values").as_slice()),
             }],
             crate::commands::DEFAULT_FLAG_PERMISSION,
+            true,
         )
         .unwrap();
         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
@@ -290,6 +291,7 @@
                 },
             ],
             crate::commands::DEFAULT_FLAG_PERMISSION,
+            true,
         )
         .unwrap();
         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
@@ -308,6 +310,7 @@
                 reader: Box::new(include_bytes!("../tests/third.values").as_slice()),
             }],
             crate::commands::DEFAULT_FLAG_PERMISSION,
+            true,
         )
         .unwrap();
         aconfig_protos::parsed_flags::try_from_binary_proto(&bytes).unwrap()
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java
index b1c7ee7..324c55d 100644
--- a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java
@@ -39,6 +39,8 @@
     /** Error code indicating that there was an error reading the Aconfig Storage file. */
     public static final int ERROR_CANNOT_READ_STORAGE_FILE = 4;
 
+    public static final int ERROR_FILE_FINGERPRINT_MISMATCH = 5;
+
     private final int mErrorCode;
 
     /**
@@ -126,6 +128,8 @@
                 return "ERROR_CONTAINER_NOT_FOUND";
             case ERROR_CANNOT_READ_STORAGE_FILE:
                 return "ERROR_CANNOT_READ_STORAGE_FILE";
+            case ERROR_FILE_FINGERPRINT_MISMATCH:
+                return "ERROR_FILE_FINGERPRINT_MISMATCH";
             default:
                 return "<Unknown error code " + mErrorCode + ">";
         }
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java
index b0b1b9b..c354873 100644
--- a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java
@@ -42,4 +42,20 @@
                 return null;
         }
     }
+
+    @Override
+    public String toString() {
+        switch (type) {
+            case 0:
+                return "PACKAGE_MAP";
+            case 1:
+                return "FLAG_MAP";
+            case 2:
+                return "FLAG_VAL";
+            case 3:
+                return "FLAG_INFO";
+            default:
+                return "unrecognized type";
+        }
+    }
 }
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/StorageFileProvider.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/StorageFileProvider.java
index 8306cc6..f1a4e26 100644
--- a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/StorageFileProvider.java
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/StorageFileProvider.java
@@ -26,7 +26,10 @@
 import java.nio.file.Paths;
 import java.nio.file.StandardOpenOption;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /** @hide */
 public class StorageFileProvider {
@@ -52,13 +55,20 @@
     }
 
     /** @hide */
-    public List<Path> listPackageMapFiles() {
-        List<Path> result = new ArrayList<>();
+    public List<String> listContainers(String[] excludes) {
+        List<String> result = new ArrayList<>();
+        Set<String> set = new HashSet<>(Arrays.asList(excludes));
+
         try {
             DirectoryStream<Path> stream =
                     Files.newDirectoryStream(Paths.get(mMapPath), "*" + PMAP_FILE_EXT);
             for (Path entry : stream) {
-                result.add(entry);
+                String fileName = entry.getFileName().toString();
+                String container =
+                        fileName.substring(0, fileName.length() - PMAP_FILE_EXT.length());
+                if (!set.contains(container)) {
+                    result.add(container);
+                }
             }
         } catch (NoSuchFileException e) {
             return result;
@@ -77,22 +87,23 @@
 
     /** @hide */
     public FlagTable getFlagTable(String container) {
-        return FlagTable.fromBytes(mapStorageFile(Paths.get(mMapPath, container + FMAP_FILE_EXT)));
+        return FlagTable.fromBytes(
+                mapStorageFile(Paths.get(mMapPath, container + FMAP_FILE_EXT), FileType.FLAG_MAP));
     }
 
     /** @hide */
     public FlagValueList getFlagValueList(String container) {
         return FlagValueList.fromBytes(
-                mapStorageFile(Paths.get(mBootPath, container + VAL_FILE_EXT)));
+                mapStorageFile(Paths.get(mBootPath, container + VAL_FILE_EXT), FileType.FLAG_VAL));
     }
 
     /** @hide */
     public static PackageTable getPackageTable(Path path) {
-        return PackageTable.fromBytes(mapStorageFile(path));
+        return PackageTable.fromBytes(mapStorageFile(path, FileType.PACKAGE_MAP));
     }
 
     // Map a storage file given file path
-    private static MappedByteBuffer mapStorageFile(Path file) {
+    private static MappedByteBuffer mapStorageFile(Path file, FileType type) {
         FileChannel channel = null;
         try {
             channel = FileChannel.open(file, StandardOpenOption.READ);
@@ -100,7 +111,7 @@
         } catch (Exception e) {
             throw new AconfigStorageException(
                     AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
-                    String.format("Fail to mmap storage file %s", file),
+                    String.format("Fail to mmap storage %s file %s", type.toString(), file),
                     e);
         } finally {
             quietlyDispose(channel);
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/StorageFileProviderTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/StorageFileProviderTest.java
index 4e90e6c..a820970 100644
--- a/tools/aconfig/aconfig_storage_file/tests/srcs/StorageFileProviderTest.java
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/StorageFileProviderTest.java
@@ -29,7 +29,6 @@
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 
@@ -37,15 +36,20 @@
 public class StorageFileProviderTest {
 
     @Test
-    public void testListpackageMapFiles() throws Exception {
+    public void testlistContainers() throws Exception {
         StorageFileProvider p =
                 new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
-        List<Path> file = p.listPackageMapFiles();
-        assertEquals(2, file.size());
+        String[] excludes = {};
+        List<String> containers = p.listContainers(excludes);
+        assertEquals(2, containers.size());
+
+        excludes = new String[] {"mock.v1"};
+        containers = p.listContainers(excludes);
+        assertEquals(1, containers.size());
 
         p = new StorageFileProvider("fake/path/", "fake/path/");
-        file = p.listPackageMapFiles();
-        assertTrue(file.isEmpty());
+        containers = p.listContainers(excludes);
+        assertTrue(containers.isEmpty());
     }
 
     @Test
@@ -56,8 +60,7 @@
         assertNotNull(pt);
         pt =
                 StorageFileProvider.getPackageTable(
-                        Paths.get(
-                                TestDataUtils.TESTDATA_PATH, "mock.v1.package.map"));
+                        Paths.get(TestDataUtils.TESTDATA_PATH, "mock.v1.package.map"));
         assertNotNull(pt);
         FlagTable f = p.getFlagTable("mock.v1");
         assertNotNull(f);
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 3238d79..09daeea 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -154,12 +154,13 @@
 java_library {
     name: "aconfig_storage_reader_java",
     srcs: [
-        "srcs/android/aconfig/storage/AconfigPackageImpl.java",
         "srcs/android/aconfig/storage/StorageInternalReader.java",
+        "srcs/android/os/flagging/PlatformAconfigPackageInternal.java",
     ],
     libs: [
         "unsupportedappusage",
         "strict_mode_stub",
+        "aconfig_storage_stub",
     ],
     static_libs: [
         "aconfig_storage_file_java",
@@ -176,8 +177,8 @@
 java_library {
     name: "aconfig_storage_reader_java_none",
     srcs: [
-        "srcs/android/aconfig/storage/AconfigPackageImpl.java",
         "srcs/android/aconfig/storage/StorageInternalReader.java",
+        "srcs/android/os/flagging/PlatformAconfigPackageInternal.java",
     ],
     libs: [
         "unsupportedappusage-sdk-none",
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java
deleted file mode 100644
index 6bf4e05..0000000
--- a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigPackageImpl.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2024 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.aconfig.storage;
-
-import android.os.StrictMode;
-
-import java.nio.file.Path;
-
-/** @hide */
-public class AconfigPackageImpl {
-    private FlagTable mFlagTable;
-    private FlagValueList mFlagValueList;
-    private PackageTable.Node mPNode;
-    private final int mPackageId;
-    private final int mBooleanStartIndex;
-
-    private AconfigPackageImpl(
-            FlagTable flagTable,
-            FlagValueList flagValueList,
-            int packageId,
-            int booleanStartIndex) {
-        this.mFlagTable = flagTable;
-        this.mFlagValueList = flagValueList;
-        this.mPackageId = packageId;
-        this.mBooleanStartIndex = booleanStartIndex;
-    }
-
-    public static AconfigPackageImpl load(String packageName, StorageFileProvider fileProvider) {
-        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-        PackageTable.Node pNode = null;
-        try {
-            // First try to find the package in the "system" container.
-            pNode = fileProvider.getPackageTable("system").get(packageName);
-        } catch (Exception e) {
-            //
-        }
-        try {
-            if (pNode != null) {
-                return new AconfigPackageImpl(
-                        fileProvider.getFlagTable("system"),
-                        fileProvider.getFlagValueList("system"),
-                        pNode.getPackageId(),
-                        pNode.getBooleanStartIndex());
-            }
-
-            // If not found in "system", search all package map files.
-            for (Path p : fileProvider.listPackageMapFiles()) {
-                PackageTable pTable = fileProvider.getPackageTable(p);
-                pNode = pTable.get(packageName);
-                if (pNode != null) {
-                    return new AconfigPackageImpl(
-                            fileProvider.getFlagTable(pTable.getHeader().getContainer()),
-                            fileProvider.getFlagValueList(pTable.getHeader().getContainer()),
-                            pNode.getPackageId(),
-                            pNode.getBooleanStartIndex());
-                }
-            }
-        } catch (AconfigStorageException e) {
-            // Consider logging the exception.
-            throw e;
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
-        }
-        // Package not found.
-        throw new AconfigStorageException(
-                AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
-                "Package " + packageName + " not found.");
-    }
-
-    public static AconfigPackageImpl load(
-            String container, String packageName, StorageFileProvider fileProvider) {
-
-        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
-        try {
-            PackageTable.Node pNode = fileProvider.getPackageTable(container).get(packageName);
-            if (pNode != null) {
-                return new AconfigPackageImpl(
-                        fileProvider.getFlagTable(container),
-                        fileProvider.getFlagValueList(container),
-                        pNode.getPackageId(),
-                        pNode.getBooleanStartIndex());
-            }
-        } catch (AconfigStorageException e) {
-            // Consider logging the exception.
-            throw e;
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
-        }
-
-        throw new AconfigStorageException(
-                AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
-                "package "
-                        + packageName
-                        + " in container "
-                        + container
-                        + " cannot be found on the device");
-    }
-
-    public boolean getBooleanFlagValue(String flagName, boolean defaultValue) {
-        FlagTable.Node fNode = mFlagTable.get(mPackageId, flagName);
-        if (fNode == null) return defaultValue;
-        return mFlagValueList.getBoolean(fNode.getFlagIndex() + mBooleanStartIndex);
-    }
-
-    public boolean getBooleanFlagValue(int index) {
-        return mFlagValueList.getBoolean(index + mBooleanStartIndex);
-    }
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/os/flagging/PlatformAconfigPackageInternal.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/os/flagging/PlatformAconfigPackageInternal.java
new file mode 100644
index 0000000..83310be
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/os/flagging/PlatformAconfigPackageInternal.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2024 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.os.flagging;
+
+import android.aconfig.storage.AconfigStorageException;
+import android.aconfig.storage.FlagTable;
+import android.aconfig.storage.FlagValueList;
+import android.aconfig.storage.PackageTable;
+import android.aconfig.storage.StorageFileProvider;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.StrictMode;
+
+/**
+ * An {@code aconfig} package containing the enabled state of its flags.
+ *
+ * <p><strong>Note: this is intended only to be used by generated code. To determine if a given flag
+ * is enabled in app code, the generated android flags should be used.</strong>
+ *
+ * <p>This class is not part of the public API and should be used by Acnofig Flag internally </b> It
+ * is intended for internal use only and will be changed or removed without notice.
+ *
+ * <p>This class is used to read the flag from Aconfig Package.Each instance of this class will
+ * cache information related to one package. To read flags from a different package, a new instance
+ * of this class should be {@link #load loaded}.
+ *
+ * @hide
+ */
+public class PlatformAconfigPackageInternal {
+
+    private final FlagTable mFlagTable;
+    private final FlagValueList mFlagValueList;
+    private final int mPackageId;
+    private final int mPackageBooleanStartOffset;
+    private final AconfigStorageException mException;
+
+    private PlatformAconfigPackageInternal(
+            FlagValueList flagValueList,
+            FlagTable flagTable,
+            int packageBooleanStartOffset,
+            int packageId,
+            AconfigStorageException exception) {
+        this.mFlagValueList = flagValueList;
+        this.mFlagTable = flagTable;
+        this.mPackageBooleanStartOffset = packageBooleanStartOffset;
+        this.mPackageId = packageId;
+        this.mException = exception;
+    }
+
+    /**
+     * Loads an Aconfig Package from platform Aconfig Storage.
+     *
+     * <p>This method is intended for internal use only and may be changed or removed without
+     * notice.
+     *
+     * <p>This method loads the specified Aconfig Package from the given container.
+     *
+     * <p>AconfigStorageException will be stored if there is an error reading from Aconfig Storage.
+     * The specific error code can be got using {@link #getException()}.
+     *
+     * @param container The name of the container.
+     * @param packageName The name of the Aconfig package to load.
+     * @return An instance of {@link PlatformAconfigPackageInternal}
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static PlatformAconfigPackageInternal load(String container, String packageName) {
+        return load(container, packageName, StorageFileProvider.getDefaultProvider());
+    }
+
+    /** @hide */
+    public static PlatformAconfigPackageInternal load(
+            String container, String packageName, StorageFileProvider fileProvider) {
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            PackageTable.Node pNode = fileProvider.getPackageTable(container).get(packageName);
+
+            if (pNode == null) {
+                return createExceptionInstance(
+                        AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
+                        "package "
+                                + packageName
+                                + " in container "
+                                + container
+                                + " cannot be found on the device");
+            }
+
+            return new PlatformAconfigPackageInternal(
+                    fileProvider.getFlagValueList(container),
+                    fileProvider.getFlagTable(container),
+                    pNode.getBooleanStartIndex(),
+                    pNode.getPackageId(),
+                    null);
+
+        } catch (AconfigStorageException e) {
+            return createExceptionInstance(e.getErrorCode(), e.getMessage());
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    /**
+     * Loads an Aconfig package from the specified container and verifies its fingerprint.
+     *
+     * <p>This method is intended for internal use only and may be changed or removed without
+     * notice.
+     *
+     * <p>AconfigStorageException will be stored if there is an error reading from Aconfig Storage.
+     * The specific error code can be got using {@link #getException()}.
+     *
+     * @param container The name of the container.
+     * @param packageName The name of the Aconfig package.
+     * @param packageFingerprint The expected fingerprint of the package.
+     * @return An instance of {@link PlatformAconfigPackageInternal} representing the loaded
+     *     package.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static PlatformAconfigPackageInternal load(
+            String container, String packageName, long packageFingerprint) {
+        return load(
+                container,
+                packageName,
+                packageFingerprint,
+                StorageFileProvider.getDefaultProvider());
+    }
+
+    /** @hide */
+    public static PlatformAconfigPackageInternal load(
+            String container,
+            String packageName,
+            long packageFingerprint,
+            StorageFileProvider fileProvider) {
+        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
+        try {
+            PackageTable.Node pNode = fileProvider.getPackageTable(container).get(packageName);
+
+            if (pNode == null) {
+                return createExceptionInstance(
+                        AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
+                        "package "
+                                + packageName
+                                + " in container "
+                                + container
+                                + " cannot be found on the device");
+            }
+
+            if (pNode.hasPackageFingerprint()
+                    && packageFingerprint != pNode.getPackageFingerprint()) {
+                return new PlatformAconfigPackageInternal(
+                        fileProvider.getFlagValueList(container),
+                        fileProvider.getFlagTable(container),
+                        pNode.getBooleanStartIndex(),
+                        pNode.getPackageId(),
+                        new AconfigStorageException(
+                                AconfigStorageException.ERROR_FILE_FINGERPRINT_MISMATCH,
+                                "The fingerprint provided for the Aconfig package "
+                                        + packageName
+                                        + " in container "
+                                        + container
+                                        + " does not match"
+                                        + " the fingerprint of the package found on the device."));
+            }
+
+            return new PlatformAconfigPackageInternal(
+                    fileProvider.getFlagValueList(container),
+                    null,
+                    pNode.getBooleanStartIndex(),
+                    0,
+                    null);
+
+        } catch (AconfigStorageException e) {
+            return createExceptionInstance(e.getErrorCode(), e.getMessage());
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    /**
+     * Retrieves the value of a boolean flag using its index.
+     *
+     * <p>This method is intended for internal use only and may be changed or removed without
+     * notice.
+     *
+     * <p>This method retrieves the value of a flag within the loaded Aconfig package using its
+     * index. The index is generated at build time and may vary between builds.
+     *
+     * <p>To ensure you are using the correct index, verify that the package's fingerprint matches
+     * the expected fingerprint before calling this method. If the fingerprints do not match, use
+     * {@link #getBooleanFlagValue(String, boolean)} instead.
+     *
+     * @param index The index of the flag within the package.
+     * @return The boolean value of the flag.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean getBooleanFlagValue(int index) {
+        return mFlagValueList.getBoolean(index + mPackageBooleanStartOffset);
+    }
+
+    /**
+     * Retrieves the value of a boolean flag using its name.
+     *
+     * <p>This method is intended for internal use only and may be changed or removed without
+     * notice.
+     *
+     * <p>This method retrieves the value of a flag within the loaded Aconfig package using its
+     * name.
+     *
+     * @param flagName The name of the flag.
+     * @param defaultValue The default value to return if the flag is not found.
+     * @return The boolean value of the flag.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean getBooleanFlagValue(String flagName, boolean defaultValue) {
+        FlagTable.Node fNode = mFlagTable.get(mPackageId, flagName);
+        if (fNode == null) {
+            return defaultValue;
+        }
+        return mFlagValueList.getBoolean(fNode.getFlagIndex() + mPackageBooleanStartOffset);
+    }
+
+    /**
+     * Returns any exception that occurred during the loading of the Aconfig package.
+     *
+     * <p>This method is intended for internal use only and may be changed or removed without
+     * notice.
+     *
+     * @return The exception that occurred, or {@code null} if no exception occurred.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public Exception getException() {
+        return mException;
+    }
+
+    /**
+     * Creates a new {@link PlatformAconfigPackageInternal} instance with an {@link
+     * AconfigStorageException}.
+     *
+     * @param errorCode The error code for the exception.
+     * @param message The error message for the exception.
+     * @return A new {@link PlatformAconfigPackageInternal} instance with the specified exception.
+     */
+    private static PlatformAconfigPackageInternal createExceptionInstance(
+            int errorCode, String message) {
+        return new PlatformAconfigPackageInternal(
+                null, null, 0, 0, new AconfigStorageException(errorCode, message));
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml b/tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadFunctionalTest.xml
similarity index 95%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml
rename to tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadFunctionalTest.xml
index 7ffa18c..ee50060 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml
+++ b/tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadFunctionalTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
-        <option name="test-file-name" value="aconfig_storage_read_api.test.java.apk" />
+        <option name="test-file-name" value="aconfig_storage_read_functional.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
@@ -45,7 +45,7 @@
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
-        <option name="package" value="android.aconfig_storage.test" />
+        <option name="package" value="android.aconfig.storage.test" />
         <option name="runtime-hint" value="1m" />
     </test>
-</configuration>
+</configuration>
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml b/tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadUnitTest.xml
similarity index 69%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml
rename to tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadUnitTest.xml
index 861b9b5..e528dd5 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidStorageJaveTest.xml
+++ b/tools/aconfig/aconfig_storage_read_api/tests/AconfigStorageReadUnitTest.xml
@@ -17,15 +17,15 @@
 <configuration description="Test aconfig storage java tests">
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="aconfig_storage_package.apk" />
+        <option name="test-file-name" value="aconfig_storage_read_unit.apk" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
-        <option name="push" value="package_v1.map->/data/local/tmp/aconfig_storage_package/testdata/mockup.package.map" />
-        <option name="push" value="flag_v1.map->/data/local/tmp/aconfig_storage_package/testdata/mockup.flag.map" />
-        <option name="push" value="flag_v1.val->/data/local/tmp/aconfig_storage_package/testdata/mockup.val" />
-        <option name="push" value="flag_v1.info->/data/local/tmp/aconfig_storage_package/testdata/mockup.info" />
-        <option name="post-push" value="chmod +r /data/local/tmp/aconfig_storage_package/testdata/" />
+        <option name="push" value="package_v2.map->/data/local/tmp/aconfig_storage_read_unit/testdata/mockup.package.map" />
+        <option name="push" value="flag_v2.map->/data/local/tmp/aconfig_storage_read_unit/testdata/mockup.flag.map" />
+        <option name="push" value="flag_v2.val->/data/local/tmp/aconfig_storage_read_unit/testdata/mockup.val" />
+        <option name="push" value="flag_v2.info->/data/local/tmp/aconfig_storage_read_unit/testdata/mockup.info" />
+        <option name="post-push" value="chmod +r /data/local/tmp/aconfig_storage_read_unit/testdata/" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.aconfig.storage.test" />
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
index 6b8942b..fb2fc77 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
@@ -48,3 +48,53 @@
         "general-tests",
     ],
 }
+
+android_test {
+    name: "aconfig_storage_read_functional",
+    srcs: [
+        "functional/srcs/**/*.java",
+    ],
+    static_libs: [
+        "aconfig_device_paths_java",
+        "aconfig_storage_file_java",
+        "aconfig_storage_reader_java",
+        "androidx.test.rules",
+        "libaconfig_storage_read_api_java",
+        "junit",
+    ],
+    jni_libs: [
+        "libaconfig_storage_read_api_rust_jni",
+    ],
+    data: [
+        ":read_api_test_storage_files",
+    ],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: [
+        "general-tests",
+    ],
+    test_config: "AconfigStorageReadFunctionalTest.xml",
+    team: "trendy_team_android_core_experiments",
+}
+
+android_test {
+    name: "aconfig_storage_read_unit",
+    team: "trendy_team_android_core_experiments",
+    srcs: [
+        "unit/srcs/**/*.java",
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "junit",
+        "aconfig_storage_reader_java",
+    ],
+    sdk_version: "test_current",
+    data: [
+        ":read_api_test_storage_files",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    test_config: "AconfigStorageReadUnitTest.xml",
+    jarjar_rules: "jarjar.txt",
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidPackageTestManifest.xml b/tools/aconfig/aconfig_storage_read_api/tests/AndroidManifest.xml
similarity index 100%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/AndroidPackageTestManifest.xml
rename to tools/aconfig/aconfig_storage_read_api/tests/AndroidManifest.xml
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/AconfigStorageReadAPITest.java
similarity index 99%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java
rename to tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/AconfigStorageReadAPITest.java
index 191741e..6dd1bce 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java
+++ b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/AconfigStorageReadAPITest.java
@@ -267,4 +267,4 @@
             assertEquals(rVal, jVal);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/PlatformAconfigPackageInternalTest.java b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/PlatformAconfigPackageInternalTest.java
new file mode 100644
index 0000000..15cfc3b
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/PlatformAconfigPackageInternalTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2024 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.aconfig.storage.test;
+
+import static android.aconfig.nano.Aconfig.ENABLED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.aconfig.DeviceProtos;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.aconfig.storage.AconfigStorageException;
+import android.aconfig.storage.FlagTable;
+import android.aconfig.storage.FlagValueList;
+import android.aconfig.storage.PackageTable;
+import android.aconfig.storage.StorageFileProvider;
+import android.os.flagging.PlatformAconfigPackageInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class PlatformAconfigPackageInternalTest {
+
+    private static final Set<String> PLATFORM_CONTAINERS = Set.of("system", "vendor", "product");
+
+    @Test
+    public void testAconfigPackageInternal_load() throws IOException {
+        List<parsed_flag> flags = DeviceProtos.loadAndParseFlagProtos();
+        Map<String, PlatformAconfigPackageInternal> readerMap = new HashMap<>();
+        StorageFileProvider fp = StorageFileProvider.getDefaultProvider();
+
+        for (parsed_flag flag : flags) {
+
+            String container = flag.container;
+            String packageName = flag.package_;
+            String flagName = flag.name;
+            if (!PLATFORM_CONTAINERS.contains(container)) continue;
+
+            PackageTable pTable = fp.getPackageTable(container);
+            PackageTable.Node pNode = pTable.get(packageName);
+            FlagTable fTable = fp.getFlagTable(container);
+            FlagTable.Node fNode = fTable.get(pNode.getPackageId(), flagName);
+            FlagValueList fList = fp.getFlagValueList(container);
+
+            int index = pNode.getBooleanStartIndex() + fNode.getFlagIndex();
+            boolean rVal = fList.getBoolean(index);
+
+            PlatformAconfigPackageInternal reader = readerMap.get(packageName);
+            if (reader == null) {
+                reader = PlatformAconfigPackageInternal.load(container, packageName);
+                assertNull(reader.getException());
+                readerMap.put(packageName, reader);
+            }
+            boolean jVal = reader.getBooleanFlagValue(flagName, !rVal);
+
+            assertEquals(rVal, jVal);
+        }
+    }
+
+    @Test
+    public void testPlatformAconfigPackageInternal_load_with_fingerprint() throws IOException {
+        List<parsed_flag> flags = DeviceProtos.loadAndParseFlagProtos();
+        Map<String, PlatformAconfigPackageInternal> readerMap = new HashMap<>();
+        StorageFileProvider fp = StorageFileProvider.getDefaultProvider();
+
+        for (parsed_flag flag : flags) {
+
+            String container = flag.container;
+            String packageName = flag.package_;
+            String flagName = flag.name;
+            if (!PLATFORM_CONTAINERS.contains(container)) continue;
+
+            PackageTable pTable = fp.getPackageTable(container);
+            PackageTable.Node pNode = pTable.get(packageName);
+            FlagTable fTable = fp.getFlagTable(container);
+            FlagTable.Node fNode = fTable.get(pNode.getPackageId(), flagName);
+            FlagValueList fList = fp.getFlagValueList(container);
+
+            int index = pNode.getBooleanStartIndex() + fNode.getFlagIndex();
+            boolean rVal = fList.getBoolean(index);
+
+            long fingerprint = pNode.getPackageFingerprint();
+
+            PlatformAconfigPackageInternal reader = readerMap.get(packageName);
+            if (reader == null) {
+                reader = PlatformAconfigPackageInternal.load(container, packageName, fingerprint);
+                assertNull(reader.getException());
+                readerMap.put(packageName, reader);
+            }
+            boolean jVal = reader.getBooleanFlagValue(fNode.getFlagIndex());
+
+            assertEquals(rVal, jVal);
+        }
+    }
+
+    @Test
+    public void testAconfigPackage_load_withError() throws IOException {
+        // container not found fake_container
+        PlatformAconfigPackageInternal aPackage =
+                PlatformAconfigPackageInternal.load("fake_container", "fake_package", 0);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) aPackage.getException()).getErrorCode());
+
+        // package not found
+        aPackage = PlatformAconfigPackageInternal.load("system", "fake_container", 0);
+        assertEquals(
+                AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
+                ((AconfigStorageException) aPackage.getException()).getErrorCode());
+
+        // fingerprint doesn't match
+        List<parsed_flag> flags = DeviceProtos.loadAndParseFlagProtos();
+        StorageFileProvider fp = StorageFileProvider.getDefaultProvider();
+
+        parsed_flag flag = flags.get(0);
+
+        String container = flag.container;
+        String packageName = flag.package_;
+        boolean value = flag.state == ENABLED;
+
+        PackageTable pTable = fp.getPackageTable(container);
+        PackageTable.Node pNode = pTable.get(packageName);
+
+        if (pNode.hasPackageFingerprint()) {
+            long fingerprint = pNode.getPackageFingerprint();
+            aPackage = PlatformAconfigPackageInternal.load(container, packageName, fingerprint + 1);
+            assertEquals(
+                    // AconfigStorageException.ERROR_FILE_FINGERPRINT_MISMATCH,
+                    5, ((AconfigStorageException) aPackage.getException()).getErrorCode());
+            assertEquals(aPackage.getBooleanFlagValue(flag.name, !value), value);
+        }
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/StorageInternalReaderTest.java
similarity index 99%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java
rename to tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/StorageInternalReaderTest.java
index 3a1bba0..8a8f054 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java
+++ b/tools/aconfig/aconfig_storage_read_api/tests/functional/srcs/StorageInternalReaderTest.java
@@ -42,4 +42,4 @@
         assertFalse(reader.getBooleanFlagValue(0));
         assertTrue(reader.getBooleanFlagValue(1));
     }
-}
+}
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt b/tools/aconfig/aconfig_storage_read_api/tests/jarjar.txt
similarity index 91%
rename from tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt
rename to tools/aconfig/aconfig_storage_read_api/tests/jarjar.txt
index 24952ec..49250d4 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/jarjar.txt
+++ b/tools/aconfig/aconfig_storage_read_api/tests/jarjar.txt
@@ -15,3 +15,5 @@
 rule android.aconfig.storage.PackageTable$* android.aconfig.storage.test.PackageTable$@1
 rule android.aconfig.storage.FlagValueList$* android.aconfig.storage.test.FlagValueList@1
 rule android.aconfig.storage.SipHasher13$* android.aconfig.storage.test.SipHasher13@1
+
+rule android.os.flagging.PlatformAconfigPackageInternal android.aconfig.storage.test.PlatformAconfigPackageInternal
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java b/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java
deleted file mode 100644
index 80c0994..0000000
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigPackageImplTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2024 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.aconfig.storage.test;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-import android.aconfig.storage.AconfigPackageImpl;
-import android.aconfig.storage.AconfigStorageException;
-import android.aconfig.storage.StorageFileProvider;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-@RunWith(JUnit4.class)
-public class AconfigPackageImplTest {
-
-    private StorageFileProvider pr;
-
-    @Before
-    public void setup() {
-        pr = new StorageFileProvider(TestDataUtils.TESTDATA_PATH, TestDataUtils.TESTDATA_PATH);
-    }
-
-    @Test
-    public void testLoad_onlyPackageName() throws Exception {
-        AconfigPackageImpl p = AconfigPackageImpl.load("com.android.aconfig.storage.test_1", pr);
-        assertNotNull(p);
-    }
-
-    @Test
-    public void testLoad_groupNameFingerprint() throws Exception {
-        AconfigPackageImpl p =
-                AconfigPackageImpl.load("mockup", "com.android.aconfig.storage.test_1", pr);
-        assertNotNull(p);
-    }
-
-    @Test
-    public void testLoad_error() throws Exception {
-        AconfigPackageImpl p;
-        // cannot find package
-        AconfigStorageException e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () ->
-                                AconfigPackageImpl.load(
-                                        "mockup", "com.android.aconfig.storage.test_10", pr));
-        assertEquals(AconfigStorageException.ERROR_PACKAGE_NOT_FOUND, e.getErrorCode());
-        // cannot find package
-        e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () -> AconfigPackageImpl.load("com.android.aconfig.storage.test_10", pr));
-        assertEquals(AconfigStorageException.ERROR_PACKAGE_NOT_FOUND, e.getErrorCode());
-        // cannot find container
-        e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () ->
-                                AconfigPackageImpl.load(
-                                        null, "com.android.aconfig.storage.test_1", pr));
-        assertEquals(AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE, e.getErrorCode());
-        e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () ->
-                                AconfigPackageImpl.load(
-                                        "test", "com.android.aconfig.storage.test_1", pr));
-        assertEquals(AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE, e.getErrorCode());
-
-        // new storage doesn't exist
-        pr = new StorageFileProvider("fake/path/", "fake/path/");
-        e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () -> AconfigPackageImpl.load("fake_package", pr));
-        assertEquals(AconfigStorageException.ERROR_PACKAGE_NOT_FOUND, e.getErrorCode());
-
-        // file read issue
-        pr = new StorageFileProvider(TestDataUtils.TESTDATA_PATH, "fake/path/");
-        e =
-                assertThrows(
-                        AconfigStorageException.class,
-                        () ->
-                                AconfigPackageImpl.load(
-                                        "mockup", "com.android.aconfig.storage.test_1", pr));
-        assertEquals(AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE, e.getErrorCode());
-    }
-
-    @Test
-    public void testGetBooleanFlagValue_flagName() throws Exception {
-        AconfigPackageImpl p =
-                AconfigPackageImpl.load("mockup", "com.android.aconfig.storage.test_1", pr);
-        assertFalse(p.getBooleanFlagValue("disabled_rw", true));
-        assertTrue(p.getBooleanFlagValue("enabled_ro", false));
-        assertTrue(p.getBooleanFlagValue("enabled_rw", false));
-        assertFalse(p.getBooleanFlagValue("fake", false));
-    }
-
-    @Test
-    public void testGetBooleanFlagValue_index() throws Exception {
-        AconfigPackageImpl p =
-                AconfigPackageImpl.load("mockup", "com.android.aconfig.storage.test_1", pr);
-        assertFalse(p.getBooleanFlagValue(0));
-        assertTrue(p.getBooleanFlagValue(1));
-        assertTrue(p.getBooleanFlagValue(2));
-    }
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
deleted file mode 100644
index 0de34a6..0000000
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-android_test {
-    name: "aconfig_storage_read_api.test.java",
-    srcs: ["./**/*.java"],
-    static_libs: [
-        "aconfig_device_paths_java",
-        "aconfig_storage_file_java",
-        "aconfig_storage_reader_java",
-        "androidx.test.rules",
-        "libaconfig_storage_read_api_java",
-        "junit",
-    ],
-    jni_libs: [
-        "libaconfig_storage_read_api_rust_jni",
-    ],
-    data: [
-        ":read_api_test_storage_files",
-    ],
-    platform_apis: true,
-    certificate: "platform",
-    test_suites: [
-        "general-tests",
-    ],
-    team: "trendy_team_android_core_experiments",
-}
-
-android_test {
-    name: "aconfig_storage_package",
-    team: "trendy_team_android_core_experiments",
-    srcs: [
-        "AconfigPackageImplTest.java",
-        "TestDataUtils.java",
-    ],
-    static_libs: [
-        "androidx.test.runner",
-        "junit",
-        "aconfig_storage_reader_java",
-    ],
-    test_config: "AndroidStorageJaveTest.xml",
-    manifest: "AndroidPackageTestManifest.xml",
-    sdk_version: "test_current",
-    data: [
-        ":read_api_test_storage_files",
-    ],
-    test_suites: [
-        "general-tests",
-    ],
-    jarjar_rules: "jarjar.txt",
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml
deleted file mode 100644
index 78bfb37..0000000
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2024 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.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android.aconfig_storage.test">
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.aconfig_storage.test" />
-
-</manifest>
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java b/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java
deleted file mode 100644
index d5cddc7..0000000
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/TestDataUtils.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2024 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.aconfig.storage.test;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-public final class TestDataUtils {
-    public static final String TEST_PACKAGE_MAP_PATH = "mockup.package.map";
-    public static final String TEST_FLAG_MAP_PATH = "mockup.flag.map";
-    public static final String TEST_FLAG_VAL_PATH = "mockup.val";
-    public static final String TEST_FLAG_INFO_PATH = "mockup.info";
-
-    public static final String TESTDATA_PATH =
-            "/data/local/tmp/aconfig_storage_package/testdata/";
-
-    public static ByteBuffer getTestPackageMapByteBuffer() throws Exception {
-        return readFile(TESTDATA_PATH + TEST_PACKAGE_MAP_PATH);
-    }
-
-    public static ByteBuffer getTestFlagMapByteBuffer() throws Exception {
-        return readFile(TESTDATA_PATH + TEST_FLAG_MAP_PATH);
-    }
-
-    public static ByteBuffer getTestFlagValByteBuffer() throws Exception {
-        return readFile(TESTDATA_PATH + TEST_FLAG_VAL_PATH);
-    }
-
-    public static ByteBuffer getTestFlagInfoByteBuffer() throws Exception {
-        return readFile(TESTDATA_PATH + TEST_FLAG_INFO_PATH);
-    }
-
-    private static ByteBuffer readFile(String fileName) throws Exception {
-        InputStream input = new FileInputStream(fileName);
-        return ByteBuffer.wrap(input.readAllBytes());
-    }
-}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/unit/srcs/PlatformAconfigPackageInternalTest.java b/tools/aconfig/aconfig_storage_read_api/tests/unit/srcs/PlatformAconfigPackageInternalTest.java
new file mode 100644
index 0000000..392f644
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/unit/srcs/PlatformAconfigPackageInternalTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2024 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.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.aconfig.storage.AconfigStorageException;
+import android.aconfig.storage.PackageTable;
+import android.aconfig.storage.StorageFileProvider;
+import android.os.flagging.PlatformAconfigPackageInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PlatformAconfigPackageInternalTest {
+
+    public static final String TESTDATA_PATH =
+            "/data/local/tmp/aconfig_storage_read_unit/testdata/";
+
+    private StorageFileProvider pr;
+
+    @Before
+    public void setup() {
+        pr = new StorageFileProvider(TESTDATA_PATH, TESTDATA_PATH);
+    }
+
+    @Test
+    public void testLoad_container_package() throws Exception {
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", pr);
+        assertNull(p.getException());
+    }
+
+    @Test
+    public void testLoad_container_package_error() throws Exception {
+        // cannot find package
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_10", pr);
+
+        assertEquals(
+                AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // cannot find container
+        p = PlatformAconfigPackageInternal.load(null, "com.android.aconfig.storage.test_1", pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+        p = PlatformAconfigPackageInternal.load("test", "com.android.aconfig.storage.test_1", pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // new storage doesn't exist
+        pr = new StorageFileProvider("fake/path/", "fake/path/");
+        p = PlatformAconfigPackageInternal.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // file read issue
+        pr = new StorageFileProvider(TESTDATA_PATH, "fake/path/");
+        p = PlatformAconfigPackageInternal.load("mockup", "com.android.aconfig.storage.test_1", pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+    }
+
+    @Test
+    public void testLoad_container_package_fingerprint() throws Exception {
+        PackageTable packageTable = pr.getPackageTable("mockup");
+
+        PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
+
+        long fingerprint = node1.getPackageFingerprint();
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertNull(p.getException());
+    }
+
+    @Test
+    public void testLoad_container_package_fingerprint_error() throws Exception {
+
+        PackageTable packageTable = pr.getPackageTable("mockup");
+
+        PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
+
+        long fingerprint = node1.getPackageFingerprint();
+
+        // cannot find package
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_10", fingerprint, pr);
+
+        assertEquals(
+                AconfigStorageException.ERROR_PACKAGE_NOT_FOUND,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // cannot find container
+        p =
+                PlatformAconfigPackageInternal.load(
+                        null, "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+        p =
+                PlatformAconfigPackageInternal.load(
+                        "test", "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+        // fingerprint doesn't match
+        p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", fingerprint + 1, pr);
+        assertEquals(
+                // AconfigStorageException.ERROR_FILE_FINGERPRINT_MISMATCH,
+                5, ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // new storage doesn't exist
+        pr = new StorageFileProvider("fake/path/", "fake/path/");
+        p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+
+        // file read issue
+        pr = new StorageFileProvider(TESTDATA_PATH, "fake/path/");
+        p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertEquals(
+                AconfigStorageException.ERROR_CANNOT_READ_STORAGE_FILE,
+                ((AconfigStorageException) p.getException()).getErrorCode());
+    }
+
+    @Test
+    public void testGetBooleanFlagValue_flagName() throws Exception {
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", pr);
+        assertFalse(p.getBooleanFlagValue("disabled_rw", true));
+        assertTrue(p.getBooleanFlagValue("enabled_ro", false));
+        assertTrue(p.getBooleanFlagValue("enabled_rw", false));
+        assertFalse(p.getBooleanFlagValue("fake", false));
+    }
+
+    @Test
+    public void testGetBooleanFlagValue_index() throws Exception {
+
+        PackageTable packageTable = pr.getPackageTable("mockup");
+
+        PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
+
+        long fingerprint = node1.getPackageFingerprint();
+        PlatformAconfigPackageInternal p =
+                PlatformAconfigPackageInternal.load(
+                        "mockup", "com.android.aconfig.storage.test_1", fingerprint, pr);
+        assertFalse(p.getBooleanFlagValue(0));
+        assertTrue(p.getBooleanFlagValue(1));
+        assertTrue(p.getBooleanFlagValue(2));
+    }
+}
diff --git a/tools/aconfig/fake_device_config/Android.bp b/tools/aconfig/fake_device_config/Android.bp
index 1f17e6b..4cd5070 100644
--- a/tools/aconfig/fake_device_config/Android.bp
+++ b/tools/aconfig/fake_device_config/Android.bp
@@ -32,3 +32,13 @@
     host_supported: true,
     is_stubs_module: true,
 }
+
+java_library {
+    name: "aconfig_storage_stub",
+    srcs: [
+        "src/android/os/flagging/**/*.java",
+    ],
+    sdk_version: "core_current",
+    host_supported: true,
+    is_stubs_module: true,
+}
diff --git a/tools/aconfig/fake_device_config/src/android/os/flagging/AconfigStorageReadException.java b/tools/aconfig/fake_device_config/src/android/os/flagging/AconfigStorageReadException.java
new file mode 100644
index 0000000..bfec98c
--- /dev/null
+++ b/tools/aconfig/fake_device_config/src/android/os/flagging/AconfigStorageReadException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2024 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.os.flagging;
+
+public class AconfigStorageReadException extends RuntimeException {
+
+    /** Generic error code indicating an unspecified Aconfig Storage error. */
+    public static final int ERROR_GENERIC = 0;
+
+    /** Error code indicating that the Aconfig Storage system is not found on the device. */
+    public static final int ERROR_STORAGE_SYSTEM_NOT_FOUND = 1;
+
+    /** Error code indicating that the requested configuration package is not found. */
+    public static final int ERROR_PACKAGE_NOT_FOUND = 2;
+
+    /** Error code indicating that the specified container is not found. */
+    public static final int ERROR_CONTAINER_NOT_FOUND = 3;
+
+    /** Error code indicating that there was an error reading the Aconfig Storage file. */
+    public static final int ERROR_CANNOT_READ_STORAGE_FILE = 4;
+
+    public static final int ERROR_FILE_FINGERPRINT_MISMATCH = 5;
+
+    public AconfigStorageReadException(int errorCode, String msg) {
+        super(msg);
+        throw new UnsupportedOperationException("Stub!");
+    }
+
+    public AconfigStorageReadException(int errorCode, String msg, Throwable cause) {
+        super(msg, cause);
+        throw new UnsupportedOperationException("Stub!");
+    }
+
+    public AconfigStorageReadException(int errorCode, Throwable cause) {
+        super(cause);
+        throw new UnsupportedOperationException("Stub!");
+    }
+
+    public int getErrorCode() {
+        throw new UnsupportedOperationException("Stub!");
+    }
+
+    @Override
+    public String getMessage() {
+        throw new UnsupportedOperationException("Stub!");
+    }
+}
diff --git a/tools/edit_monitor/daemon_manager.py b/tools/edit_monitor/daemon_manager.py
index da85c11..ef2a309 100644
--- a/tools/edit_monitor/daemon_manager.py
+++ b/tools/edit_monitor/daemon_manager.py
@@ -84,7 +84,7 @@
         "edit_monitor",
         self.user_name,
         "ENABLE_ANDROID_EDIT_MONITOR",
-        10,
+        50,
     ):
       logging.warning("Edit monitor is disabled, exiting...")
       return
diff --git a/tools/releasetools/add_img_to_target_files.py b/tools/releasetools/add_img_to_target_files.py
index c25ff27..30a6acc 100644
--- a/tools/releasetools/add_img_to_target_files.py
+++ b/tools/releasetools/add_img_to_target_files.py
@@ -1100,7 +1100,7 @@
     vbmeta_partitions = common.AVB_PARTITIONS[:] + tuple(avb_custom_partitions)
 
     vbmeta_system = OPTIONS.info_dict.get("avb_vbmeta_system", "").strip()
-    if vbmeta_system:
+    if vbmeta_system and set(vbmeta_system.split()).intersection(partitions):
       banner("vbmeta_system")
       partitions["vbmeta_system"] = AddVBMeta(
           output_zip, partitions, "vbmeta_system", vbmeta_system.split())
@@ -1110,7 +1110,7 @@
       vbmeta_partitions.append("vbmeta_system")
 
     vbmeta_vendor = OPTIONS.info_dict.get("avb_vbmeta_vendor", "").strip()
-    if vbmeta_vendor:
+    if vbmeta_vendor and set(vbmeta_vendor.split()).intersection(partitions):
       banner("vbmeta_vendor")
       partitions["vbmeta_vendor"] = AddVBMeta(
           output_zip, partitions, "vbmeta_vendor", vbmeta_vendor.split())
@@ -1137,7 +1137,7 @@
             if item not in included_partitions]
         vbmeta_partitions.append(partition_name)
 
-    if OPTIONS.info_dict.get("avb_building_vbmeta_image") == "true":
+    if OPTIONS.info_dict.get("avb_building_vbmeta_image") == "true" and set(vbmeta_partitions).intersection(partitions):
       banner("vbmeta")
       AddVBMeta(output_zip, partitions, "vbmeta", vbmeta_partitions)