From e5dd91bca7d6cca4cd6d642df775be5d8cc79b34 Mon Sep 17 00:00:00 2001 From: Dennis Shen Date: Tue, 28 May 2024 17:04:02 +0000 Subject: [PATCH] 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 --- tools/aconfig/.editorconfig | 9 + tools/aconfig/TEST_MAPPING | 10 +- .../aconfig_storage_read_api/Android.bp | 25 +++ .../storage/AconfigStorageReadAPI.java | 88 ++++++++ .../aconfig/storage/BooleanFlagValue.java | 30 +++ .../aconfig/storage/FlagReadContext.java | 56 +++++ .../aconfig/storage/PackageReadContext.java | 36 +++ .../aconfig_storage_read_api/srcs/lib.rs | 203 +++++++++++++++++ .../aconfig_storage_read_api/tests/Android.bp | 21 +- .../tests/java/AconfigStorageReadAPITest.java | 212 ++++++++++++++++++ .../tests/java/Android.bp | 21 ++ .../tests/java/AndroidManifest.xml | 26 +++ .../tests/java/AndroidTest.xml | 51 +++++ 13 files changed, 776 insertions(+), 12 deletions(-) create mode 100644 tools/aconfig/.editorconfig create mode 100644 tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/AconfigStorageReadAPI.java create mode 100644 tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/BooleanFlagValue.java create mode 100644 tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/FlagReadContext.java create mode 100644 tools/aconfig/aconfig_storage_read_api/srcs/android/aconfig/storage/PackageReadContext.java create mode 100644 tools/aconfig/aconfig_storage_read_api/srcs/lib.rs create mode 100644 tools/aconfig/aconfig_storage_read_api/tests/java/AconfigStorageReadAPITest.java create mode 100644 tools/aconfig/aconfig_storage_read_api/tests/java/Android.bp create mode 100644 tools/aconfig/aconfig_storage_read_api/tests/java/AndroidManifest.xml create mode 100644 tools/aconfig/aconfig_storage_read_api/tests/java/AndroidTest.xml 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 @@ + + + + + + + + + + + + + + + + + + + + + + +