Merge "core/Makefile: Add desktop pack image hook." into main
diff --git a/core/main.mk b/core/main.mk
index f3980f1..8d73793 100644
--- a/core/main.mk
+++ b/core/main.mk
@@ -688,11 +688,16 @@
           $(eval my_testcases := $(HOST_OUT_TESTCASES)),\
           $(eval my_testcases := $$(COMPATIBILITY_TESTCASES_OUT_$(suite))))\
         $(eval target := $(my_testcases)/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
+        $(eval link_target := ../../../$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
+        $(eval symlink := $(my_testcases)/$(m)/shared_libs/$(lastword $(subst /, ,$(dir $(f))))/$(notdir $(f)))\
+        $(eval COMPATIBILITY.$(suite).SYMLINKS := \
+          $$(COMPATIBILITY.$(suite).SYMLINKS) $(f):$(link_target):$(symlink))\
         $(if $(strip $(ALL_TARGETS.$(target).META_LIC)),,$(call declare-copy-target-license-metadata,$(target),$(f)))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
           $$(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES) $(f):$(target))\
         $(eval COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES := \
-          $(sort $(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES)))))))
+          $(sort $(COMPATIBILITY.$(suite).HOST_SHARED_LIBRARY.FILES))))))\
+  $(eval COMPATIBILITY.$(suite).SYMLINKS := $(sort $(COMPATIBILITY.$(suite).SYMLINKS))))
 endef
 
 $(call resolve-shared-libs-depes,TARGET_)
diff --git a/core/tasks/host-unit-tests.mk b/core/tasks/host-unit-tests.mk
index 733a2e2..4cb23c0 100644
--- a/core/tasks/host-unit-tests.mk
+++ b/core/tasks/host-unit-tests.mk
@@ -29,15 +29,28 @@
     $(eval _cmf_src := $(word 1,$(_cmf_tuple))) \
     $(_cmf_src)))
 
+my_symlinks_for_host_unit_tests := $(foreach f,$(COMPATIBILITY.host-unit-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)))
+
 $(host_unit_tests_zip) : PRIVATE_HOST_SHARED_LIBS := $(my_host_shared_lib_for_host_unit_tests)
 
-$(host_unit_tests_zip) : $(COMPATIBILITY.host-unit-tests.FILES) $(my_host_shared_lib_for_host_unit_tests) $(SOONG_ZIP)
+$(host_unit_tests_zip) : PRIVATE_SYMLINKS := $(my_symlinks_for_host_unit_tests)
+
+$(host_unit_tests_zip) : $(COMPATIBILITY.host-unit-tests.FILES) $(my_host_shared_lib_for_host_unit_tests) $(my_symlinks_for_host_unit_tests) $(SOONG_ZIP)
 	echo $(sort $(COMPATIBILITY.host-unit-tests.FILES)) | tr " " "\n" > $@.list
 	grep $(HOST_OUT_TESTCASES) $@.list > $@-host.list || true
 	echo "" >> $@-host-libs.list
 	$(hide) for shared_lib in $(PRIVATE_HOST_SHARED_LIBS); do \
 	  echo $$shared_lib >> $@-host-libs.list; \
 	done
+	$(hide) for symlink in $(PRIVATE_SYMLINKS); do \
+	  echo $$symlink >> $@-host.list; \
+	done
 	grep $(TARGET_OUT_TESTCASES) $@.list > $@-target.list || true
 	$(hide) $(SOONG_ZIP) -d -o $@ -P host -C $(HOST_OUT) -l $@-host.list \
 	  -P target -C $(PRODUCT_OUT) -l $@-target.list \
diff --git a/target/product/virtual_ab_ota/OWNERS b/target/product/virtual_ab_ota/OWNERS
new file mode 100644
index 0000000..8eb0686
--- /dev/null
+++ b/target/product/virtual_ab_ota/OWNERS
@@ -0,0 +1,4 @@
+zhangkelvin@google.com
+dvander@google.com
+akailash@google.com
+
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 5e9eb54..c2f4c18 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -154,7 +154,9 @@
 java_library {
     name: "libaconfig_storage_read_api_java",
     srcs: [
-        "srcs/**/*.java",
+        "srcs/android/aconfig/storage/AconfigStorageReadAPI.java",
+        "srcs/android/aconfig/storage/FlagReadContext.java",
+        "srcs/android/aconfig/storage/PackageReadContext.java",
     ],
     required: ["libaconfig_storage_read_api_rust_jni"],
     min_sdk_version: "UpsideDownCake",
@@ -163,3 +165,14 @@
         "//apex_available:platform",
     ],
 }
+
+java_library {
+    name: "aconfig_storage_reader_java",
+    srcs: [
+        "srcs/android/aconfig/storage/StorageInternalReader.java",
+    ],
+    static_libs: [
+        "aconfig_storage_file_java",
+    ],
+    sdk_version: "core_current",
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageInternalReader.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageInternalReader.java
new file mode 100644
index 0000000..5f31017
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/StorageInternalReader.java
@@ -0,0 +1,74 @@
+/*
+ * 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 java.io.FileInputStream;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+
+public class StorageInternalReader {
+
+    private static final String MAP_PATH = "/metadata/aconfig/maps/";
+    private static final String BOOT_PATH = "/metadata/aconfig/boot/";
+
+    private PackageTable mPackageTable;
+    private FlagValueList mFlagValueList;
+
+    private int mPackageBooleanStartOffset;
+
+    public StorageInternalReader(String container, String packageName) {
+        this(packageName, MAP_PATH + container + ".package.map", BOOT_PATH + container + ".val");
+    }
+
+    public StorageInternalReader(String packageName, String packageMapFile, String flagValueFile) {
+        mPackageTable = PackageTable.fromBytes(mapStorageFile(packageMapFile));
+        mFlagValueList = FlagValueList.fromBytes(mapStorageFile(flagValueFile));
+        mPackageBooleanStartOffset = getPackageBooleanStartOffset(packageName);
+    }
+
+    public boolean getBooleanFlagValue(int index) {
+        index += mPackageBooleanStartOffset;
+        if (index >= mFlagValueList.size()) {
+            throw new AconfigStorageException("Fail to get boolean flag value");
+        }
+        return mFlagValueList.get(index);
+    }
+
+    private int getPackageBooleanStartOffset(String packageName) {
+        PackageTable.Node pNode = mPackageTable.get(packageName);
+        if (pNode == null) {
+            PackageTable.Header header = mPackageTable.getHeader();
+            throw new AconfigStorageException(
+                    String.format(
+                            "Fail to get package %s from container %s",
+                            packageName, header.getContainer()));
+        }
+        return pNode.getBooleanStartIndex();
+    }
+
+    // Map a storage file given file path
+    private static MappedByteBuffer mapStorageFile(String file) {
+        try {
+            FileInputStream stream = new FileInputStream(file);
+            FileChannel channel = stream.getChannel();
+            return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
+        } catch (Exception e) {
+            throw new AconfigStorageException(
+                    String.format("Fail to mmap storage file %s", file), e);
+        }
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
index d94b2b4..11b3824 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
@@ -1,7 +1,8 @@
 android_test {
     name: "aconfig_storage_read_api.test.java",
-    srcs: ["AconfigStorageReadAPITest.java"],
+    srcs: ["./**/*.java"],
     static_libs: [
+        "aconfig_storage_reader_java",
         "androidx.test.rules",
         "libaconfig_storage_read_api_java",
         "junit",
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java b/tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java
new file mode 100644
index 0000000..3a1bba0
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/StorageInternalReaderTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.aconfig.storage.StorageInternalReader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class StorageInternalReaderTest {
+
+    private String mStorageDir = "/data/local/tmp/aconfig_java_api_test";
+
+    @Test
+    public void testStorageInternalReader_getFlag() {
+
+        String packageMapFile = mStorageDir + "/maps/mockup.package.map";
+        String flagValueFile = mStorageDir + "/boot/mockup.val";
+
+        StorageInternalReader reader =
+                new StorageInternalReader(
+                        "com.android.aconfig.storage.test_1", packageMapFile, flagValueFile);
+        assertFalse(reader.getBooleanFlagValue(0));
+        assertTrue(reader.getBooleanFlagValue(1));
+    }
+}