aconfig: create first implementation of aconfig storage java read api
1, See AconfigStorageReadAPI.java to see java APIs to map storage files
and read flag values. It is using fast native annotation, in theory it
should be faster than regular JNI without much of the overhead.
2, The java api calls into Rust wrapper in srcs/lib.rs, note that
MappedByteBuffer is not copied during JNI. In the rust side
implementation we get the underlying raw pointer and buffer size and
reconstruct a rust slice. However, at current implmentation, the string
input such as package name and flag name are most likely copied. They
are converted from JStirng to JavaStr first without copy, then the
into() call to convert it to Rust string. We could potentially optimize
it to without copy.
3, Add an android_test target to lock down the API behaviors.
Bug: b/321077378
Test: atest -c
Change-Id: I8915fe70e8eb341be563c70f85e19e644e8aa6be
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>