Implement storage files in Java
This change implements storage files in java to remove the dependencies
on JNI.
Bug: 352078117
Test: atest aconfig_storage_file.test.java
Change-Id: I34438576d09d0aa31eabc60f472e71cebf1552a0
diff --git a/tools/aconfig/TEST_MAPPING b/tools/aconfig/TEST_MAPPING
index 448d8cf..15e4187 100644
--- a/tools/aconfig/TEST_MAPPING
+++ b/tools/aconfig/TEST_MAPPING
@@ -98,6 +98,10 @@
{
// aconfig_storage file cpp integration tests
"name": "aconfig_storage_file.test.cpp"
+ },
+ {
+ // aconfig_storage file java integration tests
+ "name": "aconfig_storage_file.test.java"
}
],
"postsubmit": [
diff --git a/tools/aconfig/aconfig_storage_file/Android.bp b/tools/aconfig/aconfig_storage_file/Android.bp
index e066e31..3859194 100644
--- a/tools/aconfig/aconfig_storage_file/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/Android.bp
@@ -137,3 +137,12 @@
min_sdk_version: "29",
double_loadable: true,
}
+
+// storage file parse api java cc_library
+java_library {
+ name: "aconfig_storage_file_java",
+ srcs: [
+ "srcs/**/*.java",
+ ],
+ sdk_version: "core_current",
+}
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java
new file mode 100644
index 0000000..86a75f2
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/AconfigStorageException.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+public class AconfigStorageException extends RuntimeException {
+ public AconfigStorageException(String msg) {
+ super(msg);
+ }
+
+ public AconfigStorageException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java
new file mode 100644
index 0000000..1c72364
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/ByteBufferReader.java
@@ -0,0 +1,54 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+public class ByteBufferReader {
+
+ private ByteBuffer mByteBuffer;
+
+ public ByteBufferReader(ByteBuffer byteBuffer) {
+ this.mByteBuffer = byteBuffer;
+ this.mByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ public int readByte() {
+ return Byte.toUnsignedInt(mByteBuffer.get());
+ }
+
+ public int readShort() {
+ return Short.toUnsignedInt(mByteBuffer.getShort());
+ }
+
+ public int readInt() {
+ return this.mByteBuffer.getInt();
+ }
+
+ public String readString() {
+ int length = readInt();
+ byte[] bytes = new byte[length];
+ mByteBuffer.get(bytes, 0, length);
+ return new String(bytes, StandardCharsets.UTF_8);
+ }
+
+ public void position(int newPosition) {
+ mByteBuffer.position(newPosition);
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java
new file mode 100644
index 0000000..b0b1b9b
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FileType.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage;
+
+public enum FileType {
+ PACKAGE_MAP(0),
+ FLAG_MAP(1),
+ FLAG_VAL(2),
+ FLAG_INFO(3);
+
+ public final int type;
+
+ FileType(int type) {
+ this.type = type;
+ }
+
+ public static FileType fromInt(int index) {
+ switch (index) {
+ case 0:
+ return PACKAGE_MAP;
+ case 1:
+ return FLAG_MAP;
+ case 2:
+ return FLAG_VAL;
+ case 3:
+ return FLAG_INFO;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java
new file mode 100644
index 0000000..e85fdee
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagTable.java
@@ -0,0 +1,174 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class FlagTable {
+
+ private Header mHeader;
+ private Map<String, Node> mNodeMap;
+
+ public static FlagTable fromBytes(ByteBuffer bytes) {
+ FlagTable flagTable = new FlagTable();
+ ByteBufferReader reader = new ByteBufferReader(bytes);
+ Header header = Header.fromBytes(reader);
+ flagTable.mHeader = header;
+ flagTable.mNodeMap = new HashMap(TableUtils.getTableSize(header.mNumFlags));
+ reader.position(header.mNodeOffset);
+ for (int i = 0; i < header.mNumFlags; i++) {
+ Node node = Node.fromBytes(reader);
+ flagTable.mNodeMap.put(makeKey(node.mPackageId, node.mFlagName), node);
+ }
+ return flagTable;
+ }
+
+ public Node get(int packageId, String flagName) {
+ return mNodeMap.get(makeKey(packageId, flagName));
+ }
+
+ public Header getHeader() {
+ return mHeader;
+ }
+
+ private static String makeKey(int packageId, String flagName) {
+ StringBuilder ret = new StringBuilder();
+ return ret.append(packageId).append('/').append(flagName).toString();
+ }
+
+ public static class Header {
+
+ private int mVersion;
+ private String mContainer;
+ private FileType mFileType;
+ private int mFileSize;
+ private int mNumFlags;
+ private int mBucketOffset;
+ private int mNodeOffset;
+
+ public static Header fromBytes(ByteBufferReader reader) {
+ Header header = new Header();
+ header.mVersion = reader.readInt();
+ header.mContainer = reader.readString();
+ header.mFileType = FileType.fromInt(reader.readByte());
+ header.mFileSize = reader.readInt();
+ header.mNumFlags = reader.readInt();
+ header.mBucketOffset = reader.readInt();
+ header.mNodeOffset = reader.readInt();
+
+ if (header.mFileType != FileType.FLAG_MAP) {
+ throw new AconfigStorageException("binary file is not a flag map");
+ }
+
+ return header;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public String getContainer() {
+ return mContainer;
+ }
+
+ public FileType getFileType() {
+ return mFileType;
+ }
+
+ public int getFileSize() {
+ return mFileSize;
+ }
+
+ public int getNumFlags() {
+ return mNumFlags;
+ }
+
+ public int getBucketOffset() {
+ return mBucketOffset;
+ }
+
+ public int getNodeOffset() {
+ return mNodeOffset;
+ }
+ }
+
+ public static class Node {
+
+ private String mFlagName;
+ private FlagType mFlagType;
+ private int mPackageId;
+ private int mFlagIndex;
+ private int mNextOffset;
+
+ public static Node fromBytes(ByteBufferReader reader) {
+ Node node = new Node();
+ node.mPackageId = reader.readInt();
+ node.mFlagName = reader.readString();
+ node.mFlagType = FlagType.fromInt(reader.readShort());
+ node.mFlagIndex = reader.readShort();
+ node.mNextOffset = reader.readInt();
+ node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset;
+ return node;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mFlagName, mFlagType, mPackageId, mFlagIndex, mNextOffset);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || !(obj instanceof Node)) {
+ return false;
+ }
+
+ Node other = (Node) obj;
+ return Objects.equals(mFlagName, other.mFlagName)
+ && Objects.equals(mFlagType, other.mFlagType)
+ && mPackageId == other.mPackageId
+ && mFlagIndex == other.mFlagIndex
+ && mNextOffset == other.mNextOffset;
+ }
+
+ public String getFlagName() {
+ return mFlagName;
+ }
+
+ public FlagType getFlagType() {
+ return mFlagType;
+ }
+
+ public int getPackageId() {
+ return mPackageId;
+ }
+
+ public int getFlagIndex() {
+ return mFlagIndex;
+ }
+
+ public int getNextOffset() {
+ return mNextOffset;
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java
new file mode 100644
index 0000000..385e2d9
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagType.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+public enum FlagType {
+ ReadWriteBoolean (0),
+ ReadOnlyBoolean(1),
+ FixedReadOnlyBoolean(2);
+
+ public final int type;
+
+ FlagType(int type) {
+ this.type = type;
+ }
+
+ public static FlagType fromInt(int index) {
+ switch (index) {
+ case 0:
+ return ReadWriteBoolean;
+ case 1:
+ return ReadOnlyBoolean;
+ case 2:
+ return FixedReadOnlyBoolean;
+ default:
+ return null;
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java
new file mode 100644
index 0000000..0ddc147
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/FlagValueList.java
@@ -0,0 +1,106 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FlagValueList {
+
+ private Header mHeader;
+ private List<Boolean> mList;
+
+ private int mSize;
+
+ public static FlagValueList fromBytes(ByteBuffer bytes) {
+ FlagValueList flagValueList = new FlagValueList();
+ ByteBufferReader reader = new ByteBufferReader(bytes);
+ Header header = Header.fromBytes(reader);
+ flagValueList.mHeader = header;
+ flagValueList.mList = new ArrayList(header.mNumFlags);
+ reader.position(header.mBooleanValueOffset);
+ for (int i = 0; i < header.mNumFlags; i++) {
+ boolean val = reader.readByte() == 1;
+ flagValueList.mList.add(val);
+ }
+ flagValueList.mSize = flagValueList.mList.size();
+ return flagValueList;
+ }
+
+ public boolean get(int index) {
+ return mList.get(index);
+ }
+
+ public Header getHeader() {
+ return mHeader;
+ }
+
+ public int size() {
+ return mSize;
+ }
+
+ public static class Header {
+
+ private int mVersion;
+ private String mContainer;
+ private FileType mFileType;
+ private int mFileSize;
+ private int mNumFlags;
+ private int mBooleanValueOffset;
+
+ public static Header fromBytes(ByteBufferReader reader) {
+ Header header = new Header();
+ header.mVersion = reader.readInt();
+ header.mContainer = reader.readString();
+ header.mFileType = FileType.fromInt(reader.readByte());
+ header.mFileSize = reader.readInt();
+ header.mNumFlags = reader.readInt();
+ header.mBooleanValueOffset = reader.readInt();
+
+ if (header.mFileType != FileType.FLAG_VAL) {
+ throw new AconfigStorageException("binary file is not a flag value file");
+ }
+
+ return header;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public String getContainer() {
+ return mContainer;
+ }
+
+ public FileType getFileType() {
+ return mFileType;
+ }
+
+ public int getFileSize() {
+ return mFileSize;
+ }
+
+ public int getNumFlags() {
+ return mNumFlags;
+ }
+
+ public int getBooleanValueOffset() {
+ return mBooleanValueOffset;
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java
new file mode 100644
index 0000000..d04e1ac
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/PackageTable.java
@@ -0,0 +1,162 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class PackageTable {
+
+ private Header mHeader;
+ private Map<String, Node> mNodeMap;
+
+ public static PackageTable fromBytes(ByteBuffer bytes) {
+ PackageTable packageTable = new PackageTable();
+ ByteBufferReader reader = new ByteBufferReader(bytes);
+ Header header = Header.fromBytes(reader);
+ packageTable.mHeader = header;
+ packageTable.mNodeMap = new HashMap(TableUtils.getTableSize(header.mNumPackages));
+ reader.position(header.mNodeOffset);
+ for (int i = 0; i < header.mNumPackages; i++) {
+ Node node = Node.fromBytes(reader);
+ packageTable.mNodeMap.put(node.mPackageName, node);
+ }
+ return packageTable;
+ }
+
+ public Node get(String packageName) {
+ return mNodeMap.get(packageName);
+ }
+
+ public Header getHeader() {
+ return mHeader;
+ }
+
+ public static class Header {
+
+ private int mVersion;
+ private String mContainer;
+ private FileType mFileType;
+ private int mFileSize;
+ private int mNumPackages;
+ private int mBucketOffset;
+ private int mNodeOffset;
+
+ public static Header fromBytes(ByteBufferReader reader) {
+ Header header = new Header();
+ header.mVersion = reader.readInt();
+ header.mContainer = reader.readString();
+ header.mFileType = FileType.fromInt(reader.readByte());
+ header.mFileSize = reader.readInt();
+ header.mNumPackages = reader.readInt();
+ header.mBucketOffset = reader.readInt();
+ header.mNodeOffset = reader.readInt();
+
+ if (header.mFileType != FileType.PACKAGE_MAP) {
+ throw new AconfigStorageException("binary file is not a package map");
+ }
+
+ return header;
+ }
+
+ public int getVersion() {
+ return mVersion;
+ }
+
+ public String getContainer() {
+ return mContainer;
+ }
+
+ public FileType getFileType() {
+ return mFileType;
+ }
+
+ public int getFileSize() {
+ return mFileSize;
+ }
+
+ public int getNumPackages() {
+ return mNumPackages;
+ }
+
+ public int getBucketOffset() {
+ return mBucketOffset;
+ }
+
+ public int getNodeOffset() {
+ return mNodeOffset;
+ }
+ }
+
+ public static class Node {
+
+ private String mPackageName;
+ private int mPackageId;
+ private int mBooleanStartIndex;
+ private int mNextOffset;
+
+ public static Node fromBytes(ByteBufferReader reader) {
+ Node node = new Node();
+ node.mPackageName = reader.readString();
+ node.mPackageId = reader.readInt();
+ node.mBooleanStartIndex = reader.readInt();
+ node.mNextOffset = reader.readInt();
+ node.mNextOffset = node.mNextOffset == 0 ? -1 : node.mNextOffset;
+ return node;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPackageName, mPackageId, mBooleanStartIndex, mNextOffset);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null || !(obj instanceof Node)) {
+ return false;
+ }
+
+ Node other = (Node) obj;
+ return Objects.equals(mPackageName, other.mPackageName)
+ && mPackageId == other.mPackageId
+ && mBooleanStartIndex == other.mBooleanStartIndex
+ && mNextOffset == other.mNextOffset;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public int getPackageId() {
+ return mPackageId;
+ }
+
+ public int getBooleanStartIndex() {
+ return mBooleanStartIndex;
+ }
+
+ public int getNextOffset() {
+ return mNextOffset;
+ }
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java
new file mode 100644
index 0000000..714b53b
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/srcs/android/aconfig/storage/TableUtils.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+public class TableUtils {
+
+ private static final int[] HASH_PRIMES =
+ new int[] {
+ 7,
+ 17,
+ 29,
+ 53,
+ 97,
+ 193,
+ 389,
+ 769,
+ 1543,
+ 3079,
+ 6151,
+ 12289,
+ 24593,
+ 49157,
+ 98317,
+ 196613,
+ 393241,
+ 786433,
+ 1572869,
+ 3145739,
+ 6291469,
+ 12582917,
+ 25165843,
+ 50331653,
+ 100663319,
+ 201326611,
+ 402653189,
+ 805306457,
+ 1610612741
+ };
+
+ public static int getTableSize(int numEntries) {
+ for (int i : HASH_PRIMES) {
+ if (i < 2 * numEntries) continue;
+ return i;
+ }
+ throw new AconfigStorageException("Number of items in a hash table exceeds limit");
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/Android.bp b/tools/aconfig/aconfig_storage_file/tests/Android.bp
index 26b7800..c33127f 100644
--- a/tools/aconfig/aconfig_storage_file/tests/Android.bp
+++ b/tools/aconfig/aconfig_storage_file/tests/Android.bp
@@ -1,4 +1,3 @@
-
cc_test {
name: "aconfig_storage_file.test.cpp",
team: "trendy_team_android_core_experiments",
@@ -21,3 +20,27 @@
"general-tests",
],
}
+
+android_test {
+ name: "aconfig_storage_file.test.java",
+ team: "trendy_team_android_core_experiments",
+ srcs: [
+ "srcs/**/*.java",
+ ],
+ static_libs: [
+ "aconfig_storage_file_java",
+ "androidx.test.runner",
+ "junit",
+ ],
+ sdk_version: "test_current",
+ test_config: "AndroidStorageJaveTest.xml",
+ data: [
+ "package.map",
+ "flag.map",
+ "flag.val",
+ "flag.info",
+ ],
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml b/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml
new file mode 100644
index 0000000..5e01879
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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_file/tests/AndroidStorageJaveTest.xml b/tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml
new file mode 100644
index 0000000..2d52d44
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/AndroidStorageJaveTest.xml
@@ -0,0 +1,33 @@
+<?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="Test aconfig storage java tests">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="aconfig_storage_file.test.java.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="package.map->/data/local/tmp/aconfig_storage_file_test_java/testdata/package.map" />
+ <option name="push" value="flag.map->/data/local/tmp/aconfig_storage_file_test_java/testdata/flag.map" />
+ <option name="push" value="flag.val->/data/local/tmp/aconfig_storage_file_test_java/testdata/flag.val" />
+ <option name="push" value="flag.info->/data/local/tmp/aconfig_storage_file_test_java/testdata/flag.info" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.aconfig.storage.test" />
+ <option name="runtime-hint" value="1m" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java
new file mode 100644
index 0000000..66a8166
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/ByteBufferReaderTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.aconfig.storage.ByteBufferReader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+
+@RunWith(JUnit4.class)
+public class ByteBufferReaderTest {
+
+ @Test
+ public void testReadByte() {
+ ByteBuffer buffer = ByteBuffer.allocate(1);
+ byte expect = 10;
+ buffer.put(expect).rewind();
+
+ ByteBufferReader reader = new ByteBufferReader(buffer);
+ assertEquals(expect, reader.readByte());
+ }
+
+ @Test
+ public void testReadShort() {
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ short expect = Short.MAX_VALUE;
+ buffer.putShort(expect).rewind();
+
+ ByteBufferReader reader = new ByteBufferReader(buffer);
+ assertEquals(expect, reader.readShort());
+ }
+
+ @Test
+ public void testReadInt() {
+ ByteBuffer buffer = ByteBuffer.allocate(4);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ int expect = 10000;
+ buffer.putInt(expect).rewind();
+
+ ByteBufferReader reader = new ByteBufferReader(buffer);
+ assertEquals(expect, reader.readInt());
+ }
+
+ @Test
+ public void testReadString() {
+ String expect = "test read string";
+ byte[] bytes = expect.getBytes(StandardCharsets.UTF_8);
+
+ ByteBuffer buffer = ByteBuffer.allocate(expect.length() * 2 + 4);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(expect.length()).put(bytes).rewind();
+
+ ByteBufferReader reader = new ByteBufferReader(buffer);
+
+ assertEquals(expect, reader.readString());
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java
new file mode 100644
index 0000000..fd40d4c
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagTableTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.aconfig.storage.FileType;
+import android.aconfig.storage.FlagTable;
+import android.aconfig.storage.FlagType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagTableTest {
+
+ @Test
+ public void testFlagTable_rightHeader() throws Exception {
+ FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer());
+ FlagTable.Header header = flagTable.getHeader();
+ assertEquals(1, header.getVersion());
+ assertEquals("mockup", header.getContainer());
+ assertEquals(FileType.FLAG_MAP, header.getFileType());
+ assertEquals(321, header.getFileSize());
+ assertEquals(8, header.getNumFlags());
+ assertEquals(31, header.getBucketOffset());
+ assertEquals(99, header.getNodeOffset());
+ }
+
+ @Test
+ public void testFlagTable_rightNode() throws Exception {
+ FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer());
+
+ FlagTable.Node node1 = flagTable.get(0, "enabled_ro");
+ FlagTable.Node node2 = flagTable.get(0, "enabled_rw");
+ FlagTable.Node node3 = flagTable.get(2, "enabled_rw");
+ FlagTable.Node node4 = flagTable.get(1, "disabled_rw");
+ FlagTable.Node node5 = flagTable.get(1, "enabled_fixed_ro");
+ FlagTable.Node node6 = flagTable.get(1, "enabled_ro");
+ FlagTable.Node node7 = flagTable.get(2, "enabled_fixed_ro");
+ FlagTable.Node node8 = flagTable.get(0, "disabled_rw");
+
+ assertEquals("enabled_ro", node1.getFlagName());
+ assertEquals("enabled_rw", node2.getFlagName());
+ assertEquals("enabled_rw", node3.getFlagName());
+ assertEquals("disabled_rw", node4.getFlagName());
+ assertEquals("enabled_fixed_ro", node5.getFlagName());
+ assertEquals("enabled_ro", node6.getFlagName());
+ assertEquals("enabled_fixed_ro", node7.getFlagName());
+ assertEquals("disabled_rw", node8.getFlagName());
+
+ assertEquals(0, node1.getPackageId());
+ assertEquals(0, node2.getPackageId());
+ assertEquals(2, node3.getPackageId());
+ assertEquals(1, node4.getPackageId());
+ assertEquals(1, node5.getPackageId());
+ assertEquals(1, node6.getPackageId());
+ assertEquals(2, node7.getPackageId());
+ assertEquals(0, node8.getPackageId());
+
+ assertEquals(FlagType.ReadOnlyBoolean, node1.getFlagType());
+ assertEquals(FlagType.ReadWriteBoolean, node2.getFlagType());
+ assertEquals(FlagType.ReadWriteBoolean, node3.getFlagType());
+ assertEquals(FlagType.ReadWriteBoolean, node4.getFlagType());
+ assertEquals(FlagType.FixedReadOnlyBoolean, node5.getFlagType());
+ assertEquals(FlagType.ReadOnlyBoolean, node6.getFlagType());
+ assertEquals(FlagType.FixedReadOnlyBoolean, node7.getFlagType());
+ assertEquals(FlagType.ReadWriteBoolean, node8.getFlagType());
+
+ assertEquals(1, node1.getFlagIndex());
+ assertEquals(2, node2.getFlagIndex());
+ assertEquals(1, node3.getFlagIndex());
+ assertEquals(0, node4.getFlagIndex());
+ assertEquals(1, node5.getFlagIndex());
+ assertEquals(2, node6.getFlagIndex());
+ assertEquals(0, node7.getFlagIndex());
+ assertEquals(0, node8.getFlagIndex());
+
+ assertEquals(-1, node1.getNextOffset());
+ assertEquals(151, node2.getNextOffset());
+ assertEquals(-1, node3.getNextOffset());
+ assertEquals(-1, node4.getNextOffset());
+ assertEquals(236, node5.getNextOffset());
+ assertEquals(-1, node6.getNextOffset());
+ assertEquals(-1, node7.getNextOffset());
+ assertEquals(-1, node8.getNextOffset());
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java
new file mode 100644
index 0000000..c18590a
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/FlagValueListTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.aconfig.storage.FileType;
+import android.aconfig.storage.FlagTable;
+import android.aconfig.storage.FlagValueList;
+import android.aconfig.storage.PackageTable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class FlagValueListTest {
+
+ @Test
+ public void testFlagValueList_rightHeader() throws Exception {
+ FlagValueList flagValueList =
+ FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer());
+ FlagValueList.Header header = flagValueList.getHeader();
+ assertEquals(1, header.getVersion());
+ assertEquals("mockup", header.getContainer());
+ assertEquals(FileType.FLAG_VAL, header.getFileType());
+ assertEquals(35, header.getFileSize());
+ assertEquals(8, header.getNumFlags());
+ assertEquals(27, header.getBooleanValueOffset());
+ }
+
+ @Test
+ public void testFlagValueList_rightNode() throws Exception {
+ FlagValueList flagValueList =
+ FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer());
+
+ boolean[] expected = new boolean[] {false, true, true, false, true, true, true, true};
+ assertEquals(expected.length, flagValueList.size());
+
+ for (int i = 0; i < flagValueList.size(); i++) {
+ assertEquals(expected[i], flagValueList.get(i));
+ }
+ }
+
+ @Test
+ public void testFlagValueList_getValue() throws Exception {
+ PackageTable packageTable =
+ PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
+ FlagTable flagTable = FlagTable.fromBytes(TestDataUtils.getTestFlagMapByteBuffer());
+
+ FlagValueList flagValueList =
+ FlagValueList.fromBytes(TestDataUtils.getTestFlagValByteBuffer());
+
+ PackageTable.Node pNode = packageTable.get("com.android.aconfig.storage.test_1");
+ FlagTable.Node fNode = flagTable.get(pNode.getPackageId(), "enabled_rw");
+ assertTrue(flagValueList.get(pNode.getBooleanStartIndex() + fNode.getFlagIndex()));
+
+ pNode = packageTable.get("com.android.aconfig.storage.test_4");
+ fNode = flagTable.get(pNode.getPackageId(), "enabled_fixed_ro");
+ assertTrue(flagValueList.get(pNode.getBooleanStartIndex() + fNode.getFlagIndex()));
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
new file mode 100644
index 0000000..e7e19d8
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/PackageTableTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.aconfig.storage.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.aconfig.storage.FileType;
+import android.aconfig.storage.PackageTable;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PackageTableTest {
+
+ @Test
+ public void testPackageTable_rightHeader() throws Exception {
+ PackageTable packageTable =
+ PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
+ PackageTable.Header header = packageTable.getHeader();
+ assertEquals(1, header.getVersion());
+ assertEquals("mockup", header.getContainer());
+ assertEquals(FileType.PACKAGE_MAP, header.getFileType());
+ assertEquals(209, header.getFileSize());
+ assertEquals(3, header.getNumPackages());
+ assertEquals(31, header.getBucketOffset());
+ assertEquals(59, header.getNodeOffset());
+ }
+
+ @Test
+ public void testPackageTable_rightNode() throws Exception {
+ PackageTable packageTable =
+ PackageTable.fromBytes(TestDataUtils.getTestPackageMapByteBuffer());
+
+ PackageTable.Node node1 = packageTable.get("com.android.aconfig.storage.test_1");
+ PackageTable.Node node2 = packageTable.get("com.android.aconfig.storage.test_2");
+ PackageTable.Node node4 = packageTable.get("com.android.aconfig.storage.test_4");
+
+ assertEquals("com.android.aconfig.storage.test_1", node1.getPackageName());
+ assertEquals("com.android.aconfig.storage.test_2", node2.getPackageName());
+ assertEquals("com.android.aconfig.storage.test_4", node4.getPackageName());
+
+ assertEquals(0, node1.getPackageId());
+ assertEquals(1, node2.getPackageId());
+ assertEquals(2, node4.getPackageId());
+
+ assertEquals(0, node1.getBooleanStartIndex());
+ assertEquals(3, node2.getBooleanStartIndex());
+ assertEquals(6, node4.getBooleanStartIndex());
+
+ assertEquals(159, node1.getNextOffset());
+ assertEquals(-1, node2.getNextOffset());
+ assertEquals(-1, node4.getNextOffset());
+ }
+}
diff --git a/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java b/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java
new file mode 100644
index 0000000..f35952d
--- /dev/null
+++ b/tools/aconfig/aconfig_storage_file/tests/srcs/TestDataUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.FileInputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+public final class TestDataUtils {
+ private static final String TEST_PACKAGE_MAP_PATH = "package.map";
+ private static final String TEST_FLAG_MAP_PATH = "flag.map";
+ private static final String TEST_FLAG_VAL_PATH = "flag.val";
+ private static final String TEST_FLAG_INFO_PATH = "flag.info";
+
+ private static final String TESTDATA_PATH =
+ "/data/local/tmp/aconfig_storage_file_test_java/testdata/";
+
+ public static ByteBuffer getTestPackageMapByteBuffer() throws Exception {
+ return readFile(TESTDATA_PATH + TEST_PACKAGE_MAP_PATH);
+ }
+
+ public static ByteBuffer getTestFlagMapByteBuffer() throws Exception {
+ return readFile(TESTDATA_PATH + TEST_FLAG_MAP_PATH);
+ }
+
+ public static ByteBuffer getTestFlagValByteBuffer() throws Exception {
+ return readFile(TESTDATA_PATH + TEST_FLAG_VAL_PATH);
+ }
+
+ public static ByteBuffer getTestFlagInfoByteBuffer() throws Exception {
+ return readFile(TESTDATA_PATH + TEST_FLAG_INFO_PATH);
+ }
+
+ private static ByteBuffer readFile(String fileName) throws Exception {
+ InputStream input = new FileInputStream(fileName);
+ return ByteBuffer.wrap(input.readAllBytes());
+ }
+}