diff --git a/tools/aconfig/.editorconfig b/tools/aconfig/.editorconfig new file mode 100644 index 0000000000..cc5985f843 --- /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 421e94a8a6..448d8cf8af 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" - } - ], - "postsubmit": [ + }, { // aconfig_storage file cpp integration tests "name": "aconfig_storage_file.test.cpp" } + ], + "postsubmit": [ + { + // 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 9b842546f5..3b124b15c1 100644 --- a/tools/aconfig/aconfig_storage_read_api/Android.bp +++ b/tools/aconfig/aconfig_storage_read_api/Android.bp @@ -111,3 +111,28 @@ cc_defaults { "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 0000000000..7746b58f32 --- /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 0000000000..11fe447928 --- /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 0000000000..57a36cafd2 --- /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 0000000000..60d6b663a2 --- /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 0000000000..e195eb8caa --- /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> { + // 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> { + // 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 { + 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 98944d60dc..ed0c728215 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 @@ rust_test { "librand", ], data: [ - "package.map", - "flag.map", - "flag.val", - "flag.info", + ":read_api_test_storage_files", ], test_suites: ["general-tests"], } @@ -30,10 +36,7 @@ cc_test { "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 0000000000..cf4cfe6909 --- /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 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 0000000000..d94b2b438c --- /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 0000000000..78bfb37dc9 --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + 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 0000000000..99c9e2566e --- /dev/null +++ b/tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + +