Merge "Deliver inheritance_graph-$(TARGET_PRODUCT).dot" into main
diff --git a/tools/aconfig/.editorconfig b/tools/aconfig/.editorconfig
new file mode 100644
index 0000000..cc5985f
--- /dev/null
+++ b/tools/aconfig/.editorconfig
@@ -0,0 +1,9 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+[*.java]
+indent_style = tab
+indent_size = 4
+
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 421e94a..448d8cf 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -94,12 +94,16 @@
     {
       // aconfig_storage read api cpp integration tests
       "name": "aconfig_storage_read_api.test.cpp"
+    },
+    {
+      // aconfig_storage file cpp integration tests
+      "name": "aconfig_storage_file.test.cpp"
     }
   ],
   "postsubmit": [
     {
-      // aconfig_storage file cpp integration tests
-      "name": "aconfig_storage_file.test.cpp"
+      // aconfig_storage read api java integration tests
+      "name": "aconfig_storage_read_api.test.java"
     }
   ]
 }
diff --git a/tools/aconfig/aconfig_storage_read_api/Android.bp b/tools/aconfig/aconfig_storage_read_api/Android.bp
index 9b84254..3b124b1 100644
--- a/tools/aconfig/aconfig_storage_read_api/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/Android.bp
@@ -111,3 +111,28 @@
         "liblog",
     ],
 }
+
+rust_ffi_shared {
+    name: "libaconfig_storage_read_api_rust_jni",
+    crate_name: "aconfig_storage_read_api_rust_jni",
+    srcs: ["srcs/lib.rs"],
+    rustlibs: [
+        "libaconfig_storage_read_api",
+        "libanyhow",
+        "libjni",
+    ],
+    prefer_rlib: true,
+}
+
+java_library {
+    name: "libaconfig_storage_read_api_java",
+    srcs: [
+        "srcs/**/*.java",
+    ],
+    required: ["libaconfig_storage_read_api_rust_jni"],
+    min_sdk_version: "UpsideDownCake",
+    apex_available: [
+        "//apex_available:anyapex",
+        "//apex_available:platform",
+    ],
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigStorageReadAPI.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigStorageReadAPI.java
new file mode 100644
index 0000000..7746b58
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigStorageReadAPI.java
@@ -0,0 +1,88 @@
+/*
+ * 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.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+
+import android.aconfig.storage.PackageReadContext;
+import android.aconfig.storage.FlagReadContext;
+import android.aconfig.storage.BooleanFlagValue;
+
+import dalvik.annotation.optimization.FastNative;
+
+public class AconfigStorageReadAPI {
+
+    // Storage file dir on device
+    private static final String STORAGEDIR = "/metadata/aconfig";
+
+    // Stoarge file type
+    public enum StorageFileType {
+        PACKAGE_MAP,
+        FLAG_MAP,
+        FLAG_VAL,
+        FLAG_INFO
+    }
+
+    // Map a storage file given file path
+    public static MappedByteBuffer mapStorageFile(String file) throws IOException {
+        FileInputStream stream = new FileInputStream(file);
+        FileChannel channel = stream.getChannel();
+        return channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
+    }
+
+    // Map a storage file given container and file type
+    public static MappedByteBuffer getMappedFile(
+        String container,
+        StorageFileType type) throws IOException{
+        switch (type) {
+            case PACKAGE_MAP:
+                return mapStorageFile(STORAGEDIR + "/maps/" + container + ".package.map");
+            case FLAG_MAP:
+                return mapStorageFile(STORAGEDIR + "/maps/" + container + ".flag.map");
+            case FLAG_VAL:
+                return mapStorageFile(STORAGEDIR + "/boot/" + container + ".val");
+            case FLAG_INFO:
+                return mapStorageFile(STORAGEDIR + "/boot/" + container + ".info");
+            default:
+                throw new IOException("Invalid storage file type");
+        }
+    }
+
+    // JNI interface to get package read context
+    @FastNative
+    public static native PackageReadContext getPackageReadContext(
+        ByteBuffer mappedFile, String packageName);
+
+    // JNI interface to get flag read context
+    @FastNative
+    public static native FlagReadContext getFlagReadContext(
+        ByteBuffer mappedFile, int packageId, String flagName);
+
+    // JNI interface to get boolean flag value
+    @FastNative
+    public static native BooleanFlagValue getBooleanFlagValue(
+        ByteBuffer mappedFile, int flagIndex);
+
+    static {
+        System.loadLibrary("aconfig_storage_read_api_rust_jni");
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/BooleanFlagValue.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/BooleanFlagValue.java
new file mode 100644
index 0000000..11fe447
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/BooleanFlagValue.java
@@ -0,0 +1,30 @@
+package android.aconfig.storage;
+/*
+ * 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.
+ */
+
+public class BooleanFlagValue {
+    public boolean mQuerySuccess;
+    public String mErrorMessage;
+    public boolean mFlagValue;
+
+    public BooleanFlagValue(boolean querySuccess,
+            String errorMessage,
+            boolean value) {
+        mQuerySuccess = querySuccess;
+        mErrorMessage = errorMessage;
+        mFlagValue = value;
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/FlagReadContext.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/FlagReadContext.java
new file mode 100644
index 0000000..57a36ca
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/FlagReadContext.java
@@ -0,0 +1,56 @@
+package android.aconfig.storage;
+/*
+ * 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.
+ */
+
+public class FlagReadContext {
+    public boolean mQuerySuccess;
+    public String mErrorMessage;
+    public boolean mFlagExists;
+    public StoredFlagType mFlagType;
+    public int mFlagIndex;
+
+    public FlagReadContext(boolean querySuccess,
+            String errorMessage,
+            boolean flagExists,
+            int flagType,
+            int flagIndex) {
+        mQuerySuccess = querySuccess;
+        mErrorMessage = errorMessage;
+        mFlagExists = flagExists;
+        mFlagType = StoredFlagType.fromInteger(flagType);
+        mFlagIndex = flagIndex;
+    }
+
+    // Flag type enum, consistent with the definition in aconfig_storage_file/src/lib.rs
+    public enum StoredFlagType {
+        ReadWriteBoolean,
+        ReadOnlyBoolean,
+        FixedReadOnlyBoolean;
+
+        public static StoredFlagType fromInteger(int x) {
+            switch(x) {
+                case 0:
+                    return ReadWriteBoolean;
+                case 1:
+                    return ReadOnlyBoolean;
+                case 2:
+                    return FixedReadOnlyBoolean;
+                default:
+                    return null;
+            }
+        }
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/PackageReadContext.java b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/PackageReadContext.java
new file mode 100644
index 0000000..60d6b66
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/PackageReadContext.java
@@ -0,0 +1,36 @@
+package android.aconfig.storage;
+/*
+ * 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.
+ */
+
+public class PackageReadContext {
+    public boolean mQuerySuccess;
+    public String mErrorMessage;
+    public boolean mPackageExists;
+    public int mPackageId;
+    public int mBooleanStartIndex;
+
+    public PackageReadContext(boolean querySuccess,
+                              String errorMessage,
+                              boolean packageExists,
+                              int packageId,
+                              int booleanStartIndex) {
+        mQuerySuccess = querySuccess;
+        mErrorMessage = errorMessage;
+        mPackageExists = packageExists;
+        mPackageId = packageId;
+        mBooleanStartIndex = booleanStartIndex;
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/srcs/lib.rs b/tools/aconfig/aconfig_storage_read_api/srcs/lib.rs
new file mode 100644
index 0000000..e195eb8
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/srcs/lib.rs
@@ -0,0 +1,203 @@
+//! aconfig storage read api java rust interlop
+
+use aconfig_storage_read_api::flag_table_query::find_flag_read_context;
+use aconfig_storage_read_api::flag_value_query::find_boolean_flag_value;
+use aconfig_storage_read_api::package_table_query::find_package_read_context;
+use aconfig_storage_read_api::{FlagReadContext, PackageReadContext};
+
+use anyhow::Result;
+use jni::objects::{JByteBuffer, JClass, JString, JValue};
+use jni::sys::{jint, jobject};
+use jni::JNIEnv;
+
+/// Call rust find package read context
+fn get_package_read_context_java(
+    env: &mut JNIEnv,
+    file: JByteBuffer,
+    package: JString,
+) -> Result<Option<PackageReadContext>> {
+    // SAFETY:
+    // The safety here is ensured as the package name is guaranteed to be a java string
+    let package_name: String = unsafe { env.get_string_unchecked(&package)?.into() };
+    let buffer_ptr = env.get_direct_buffer_address(&file)?;
+    let buffer_size = env.get_direct_buffer_capacity(&file)?;
+    // SAFETY:
+    // The safety here is ensured as only non null MemoryMappedBuffer will be passed in,
+    // so the conversion to slice is guaranteed to be valid
+    let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) };
+    Ok(find_package_read_context(buffer, &package_name)?)
+}
+
+/// Create java package read context return
+fn create_java_package_read_context(
+    env: &mut JNIEnv,
+    success_query: bool,
+    error_message: String,
+    pkg_found: bool,
+    pkg_id: u32,
+    start_index: u32,
+) -> jobject {
+    let query_success = JValue::Bool(success_query as u8);
+    let errmsg = env.new_string(error_message).expect("failed to create JString");
+    let package_exists = JValue::Bool(pkg_found as u8);
+    let package_id = JValue::Int(pkg_id as i32);
+    let boolean_start_index = JValue::Int(start_index as i32);
+    let context = env.new_object(
+        "android/aconfig/storage/PackageReadContext",
+        "(ZLjava/lang/String;ZII)V",
+        &[query_success, (&errmsg).into(), package_exists, package_id, boolean_start_index],
+    );
+    context.expect("failed to call PackageReadContext constructor").into_raw()
+}
+
+/// Get package read context JNI
+#[no_mangle]
+#[allow(unused)]
+pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getPackageReadContext<
+    'local,
+>(
+    mut env: JNIEnv<'local>,
+    class: JClass<'local>,
+    file: JByteBuffer<'local>,
+    package: JString<'local>,
+) -> jobject {
+    match get_package_read_context_java(&mut env, file, package) {
+        Ok(context_opt) => match context_opt {
+            Some(context) => create_java_package_read_context(
+                &mut env,
+                true,
+                String::from(""),
+                true,
+                context.package_id,
+                context.boolean_start_index,
+            ),
+            None => create_java_package_read_context(&mut env, true, String::from(""), false, 0, 0),
+        },
+        Err(errmsg) => {
+            create_java_package_read_context(&mut env, false, format!("{:?}", errmsg), false, 0, 0)
+        }
+    }
+}
+
+/// Call rust find flag read context
+fn get_flag_read_context_java(
+    env: &mut JNIEnv,
+    file: JByteBuffer,
+    package_id: jint,
+    flag: JString,
+) -> Result<Option<FlagReadContext>> {
+    // SAFETY:
+    // The safety here is ensured as the flag name is guaranteed to be a java string
+    let flag_name: String = unsafe { env.get_string_unchecked(&flag)?.into() };
+    let buffer_ptr = env.get_direct_buffer_address(&file)?;
+    let buffer_size = env.get_direct_buffer_capacity(&file)?;
+    // SAFETY:
+    // The safety here is ensured as only non null MemoryMappedBuffer will be passed in,
+    // so the conversion to slice is guaranteed to be valid
+    let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) };
+    Ok(find_flag_read_context(buffer, package_id as u32, &flag_name)?)
+}
+
+/// Create java flag read context return
+fn create_java_flag_read_context(
+    env: &mut JNIEnv,
+    success_query: bool,
+    error_message: String,
+    flg_found: bool,
+    flg_type: u32,
+    flg_index: u32,
+) -> jobject {
+    let query_success = JValue::Bool(success_query as u8);
+    let errmsg = env.new_string(error_message).expect("failed to create JString");
+    let flag_exists = JValue::Bool(flg_found as u8);
+    let flag_type = JValue::Int(flg_type as i32);
+    let flag_index = JValue::Int(flg_index as i32);
+    let context = env.new_object(
+        "android/aconfig/storage/FlagReadContext",
+        "(ZLjava/lang/String;ZII)V",
+        &[query_success, (&errmsg).into(), flag_exists, flag_type, flag_index],
+    );
+    context.expect("failed to call FlagReadContext constructor").into_raw()
+}
+
+/// Get flag read context JNI
+#[no_mangle]
+#[allow(unused)]
+pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getFlagReadContext<
+    'local,
+>(
+    mut env: JNIEnv<'local>,
+    class: JClass<'local>,
+    file: JByteBuffer<'local>,
+    package_id: jint,
+    flag: JString<'local>,
+) -> jobject {
+    match get_flag_read_context_java(&mut env, file, package_id, flag) {
+        Ok(context_opt) => match context_opt {
+            Some(context) => create_java_flag_read_context(
+                &mut env,
+                true,
+                String::from(""),
+                true,
+                context.flag_type as u32,
+                context.flag_index as u32,
+            ),
+            None => create_java_flag_read_context(&mut env, true, String::from(""), false, 9999, 0),
+        },
+        Err(errmsg) => {
+            create_java_flag_read_context(&mut env, false, format!("{:?}", errmsg), false, 9999, 0)
+        }
+    }
+}
+
+/// Create java boolean flag value return
+fn create_java_boolean_flag_value(
+    env: &mut JNIEnv,
+    success_query: bool,
+    error_message: String,
+    value: bool,
+) -> jobject {
+    let query_success = JValue::Bool(success_query as u8);
+    let errmsg = env.new_string(error_message).expect("failed to create JString");
+    let flag_value = JValue::Bool(value as u8);
+    let context = env.new_object(
+        "android/aconfig/storage/BooleanFlagValue",
+        "(ZLjava/lang/String;Z)V",
+        &[query_success, (&errmsg).into(), flag_value],
+    );
+    context.expect("failed to call BooleanFlagValue constructor").into_raw()
+}
+
+/// Call rust find boolean flag value
+fn get_boolean_flag_value_java(
+    env: &mut JNIEnv,
+    file: JByteBuffer,
+    flag_index: jint,
+) -> Result<bool> {
+    let buffer_ptr = env.get_direct_buffer_address(&file)?;
+    let buffer_size = env.get_direct_buffer_capacity(&file)?;
+    // SAFETY:
+    // The safety here is ensured as only non null MemoryMappedBuffer will be passed in,
+    // so the conversion to slice is guaranteed to be valid
+    let buffer = unsafe { std::slice::from_raw_parts(buffer_ptr, buffer_size) };
+    Ok(find_boolean_flag_value(buffer, flag_index as u32)?)
+}
+
+/// Get flag value JNI
+#[no_mangle]
+#[allow(unused)]
+pub extern "system" fn Java_android_aconfig_storage_AconfigStorageReadAPI_getBooleanFlagValue<
+    'local,
+>(
+    mut env: JNIEnv<'local>,
+    class: JClass<'local>,
+    file: JByteBuffer<'local>,
+    flag_index: jint,
+) -> jobject {
+    match get_boolean_flag_value_java(&mut env, file, flag_index) {
+        Ok(value) => create_java_boolean_flag_value(&mut env, true, String::from(""), value),
+        Err(errmsg) => {
+            create_java_boolean_flag_value(&mut env, false, format!("{:?}", errmsg), false)
+        }
+    }
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
index 98944d6..ed0c728 100644
--- a/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
+++ b/tools/aconfig/aconfig_storage_read_api/tests/Android.bp
@@ -1,7 +1,16 @@
+filegroup {
+    name: "read_api_test_storage_files",
+    srcs: ["package.map",
+        "flag.map",
+        "flag.val",
+        "flag.info"
+    ],
+}
+
 rust_test {
     name: "aconfig_storage_read_api.test.rust",
     srcs: [
-        "storage_read_api_test.rs"
+        "storage_read_api_test.rs",
     ],
     rustlibs: [
         "libanyhow",
@@ -10,10 +19,7 @@
         "librand",
     ],
     data: [
-        "package.map",
-        "flag.map",
-        "flag.val",
-        "flag.info",
+        ":read_api_test_storage_files",
     ],
     test_suites: ["general-tests"],
 }
@@ -30,10 +36,7 @@
         "liblog",
     ],
     data: [
-        "package.map",
-        "flag.map",
-        "flag.val",
-        "flag.info",
+        ":read_api_test_storage_files",
     ],
     test_suites: [
         "device-tests",
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java b/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java
new file mode 100644
index 0000000..cf4cfe6
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java
@@ -0,0 +1,212 @@
+/*
+ * 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.IOException;
+import java.nio.MappedByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.aconfig.storage.AconfigStorageReadAPI;
+import android.aconfig.storage.PackageReadContext;
+import android.aconfig.storage.FlagReadContext;
+import android.aconfig.storage.FlagReadContext.StoredFlagType;
+import android.aconfig.storage.BooleanFlagValue;
+
+@RunWith(JUnit4.class)
+public class AconfigStorageReadAPITest{
+
+    private String mStorageDir = "/data/local/tmp/aconfig_java_api_test";
+
+    @Test
+    public void testPackageContextQuery() {
+        MappedByteBuffer packageMap = null;
+        try {
+            packageMap = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/maps/mockup.package.map");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(packageMap != null);
+
+        PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext(
+            packageMap, "com.android.aconfig.storage.test_1");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertTrue(context.mPackageExists);
+        assertEquals(context.mPackageId, 0);
+        assertEquals(context.mBooleanStartIndex, 0);
+
+        context = AconfigStorageReadAPI.getPackageReadContext(
+            packageMap, "com.android.aconfig.storage.test_2");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertTrue(context.mPackageExists);
+        assertEquals(context.mPackageId, 1);
+        assertEquals(context.mBooleanStartIndex, 3);
+
+        context = AconfigStorageReadAPI.getPackageReadContext(
+            packageMap, "com.android.aconfig.storage.test_4");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertTrue(context.mPackageExists);
+        assertEquals(context.mPackageId, 2);
+        assertEquals(context.mBooleanStartIndex, 6);
+    }
+
+    @Test
+    public void testNonExistPackageContextQuery() {
+        MappedByteBuffer packageMap = null;
+        try {
+            packageMap = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/maps/mockup.package.map");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(packageMap != null);
+
+        PackageReadContext context = AconfigStorageReadAPI.getPackageReadContext(
+            packageMap, "unknown");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertFalse(context.mPackageExists);
+        assertEquals(context.mPackageId, 0);
+        assertEquals(context.mBooleanStartIndex, 0);
+    }
+
+    @Test
+    public void testFlagContextQuery() {
+        MappedByteBuffer flagMap = null;
+        try {
+            flagMap = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/maps/mockup.flag.map");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(flagMap!= null);
+
+        class Baseline {
+            public int mPackageId;
+            public String mFlagName;
+            public StoredFlagType mFlagType;
+            public int mFlagIndex;
+
+            public Baseline(int packageId,
+                    String flagName,
+                    StoredFlagType flagType,
+                    int flagIndex) {
+                mPackageId = packageId;
+                mFlagName = flagName;
+                mFlagType = flagType;
+                mFlagIndex = flagIndex;
+            }
+        }
+
+        List<Baseline> baselines = new ArrayList();
+        baselines.add(new Baseline(0, "enabled_ro", StoredFlagType.ReadOnlyBoolean, 1));
+        baselines.add(new Baseline(0, "enabled_rw", StoredFlagType.ReadWriteBoolean, 2));
+        baselines.add(new Baseline(2, "enabled_rw", StoredFlagType.ReadWriteBoolean, 1));
+        baselines.add(new Baseline(1, "disabled_rw", StoredFlagType.ReadWriteBoolean, 0));
+        baselines.add(new Baseline(1, "enabled_fixed_ro", StoredFlagType.FixedReadOnlyBoolean, 1));
+        baselines.add(new Baseline(1, "enabled_ro", StoredFlagType.ReadOnlyBoolean, 2));
+        baselines.add(new Baseline(2, "enabled_fixed_ro", StoredFlagType.FixedReadOnlyBoolean, 0));
+        baselines.add(new Baseline(0, "disabled_rw", StoredFlagType.ReadWriteBoolean, 0));
+
+        for (Baseline baseline : baselines) {
+            FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext(
+                flagMap, baseline.mPackageId,  baseline.mFlagName);
+            assertTrue(context.mQuerySuccess);
+            assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+            assertTrue(context.mFlagExists);
+            assertEquals(context.mFlagType, baseline.mFlagType);
+            assertEquals(context.mFlagIndex, baseline.mFlagIndex);
+        }
+    }
+
+    @Test
+    public void testNonExistFlagContextQuery() {
+        MappedByteBuffer flagMap = null;
+        try {
+            flagMap = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/maps/mockup.flag.map");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(flagMap!= null);
+
+        FlagReadContext context = AconfigStorageReadAPI.getFlagReadContext(
+            flagMap, 0,  "unknown");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertFalse(context.mFlagExists);
+        assertEquals(context.mFlagType, null);
+        assertEquals(context.mFlagIndex, 0);
+
+        context = AconfigStorageReadAPI.getFlagReadContext(
+            flagMap, 3,  "enabled_ro");
+        assertTrue(context.mQuerySuccess);
+        assertTrue(context.mErrorMessage, context.mErrorMessage.equals(""));
+        assertFalse(context.mFlagExists);
+        assertEquals(context.mFlagType, null);
+        assertEquals(context.mFlagIndex, 0);
+    }
+
+    @Test
+    public void testBooleanFlagValueQuery() {
+        MappedByteBuffer flagVal = null;
+        try {
+            flagVal = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/boot/mockup.val");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(flagVal!= null);
+
+        boolean[] baselines = {false, true, true, false, true, true, true, true};
+        for (int i = 0; i < 8; ++i) {
+            BooleanFlagValue value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, i);
+            assertTrue(value.mQuerySuccess);
+            assertTrue(value.mErrorMessage, value.mErrorMessage.equals(""));
+            assertEquals(value.mFlagValue, baselines[i]);
+        }
+    }
+
+    @Test
+    public void testInvalidBooleanFlagValueQuery() {
+        MappedByteBuffer flagVal = null;
+        try {
+            flagVal = AconfigStorageReadAPI.mapStorageFile(
+                mStorageDir + "/boot/mockup.val");
+        } catch(IOException ex){
+            assertTrue(ex.toString(), false);
+        }
+        assertTrue(flagVal!= null);
+
+        BooleanFlagValue value = AconfigStorageReadAPI.getBooleanFlagValue(flagVal, 9);
+        String expectedErrmsg = "Flag value offset goes beyond the end of the file";
+        assertFalse(value.mQuerySuccess);
+        assertTrue(value.mErrorMessage, value.mErrorMessage.contains(expectedErrmsg));
+    }
+ }
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
new file mode 100644
index 0000000..d94b2b4
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp
@@ -0,0 +1,21 @@
+android_test {
+    name: "aconfig_storage_read_api.test.java",
+    srcs: ["AconfigStorageReadAPITest.java"],
+    static_libs: [
+        "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",
+}
diff --git a/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml
new file mode 100644
index 0000000..78bfb37
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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/AndroidTest.xml b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml
new file mode 100644
index 0000000..99c9e25
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?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.
+-->
+
+<configuration description="Config for aconfig storage read java api tests">
+    <!-- Need root to start virtualizationservice -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <!-- Prepare test directories. -->
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/aconfig_java_api" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/aconfig_java_api" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="test-file-name" value="aconfig_storage_read_api.test.java.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+    <!-- Test data files -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="abort-on-push-failure" value="true" />
+        <option name="push-file" key="package.map"
+                value="/data/local/tmp/aconfig_java_api_test/maps/mockup.package.map" />
+        <option name="push-file" key="flag.map"
+                value="/data/local/tmp/aconfig_java_api_test/maps/mockup.flag.map" />
+        <option name="push-file" key="flag.val"
+                value="/data/local/tmp/aconfig_java_api_test/boot/mockup.val" />
+    </target_preparer>
+
+    <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="runtime-hint" value="1m" />
+    </test>
+</configuration>