Add Telephony satellite on-device access control module and tools
Bug: 313773568
Test: atest SatelliteToolsTests TeleServiceTests
Change-Id: I096310b71ec92beffed42321ed4205ac085dcf8c
diff --git a/utils/satellite/tools/Android.bp b/utils/satellite/tools/Android.bp
new file mode 100644
index 0000000..9aacdd9
--- /dev/null
+++ b/utils/satellite/tools/Android.bp
@@ -0,0 +1,71 @@
+// Copyright (C) 2020 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+ name: "satellite-s2storage-tools",
+ srcs: [
+ "src/main/java/**/*.java",
+ ],
+ static_libs: [
+ "jcommander",
+ "guava",
+ "satellite-s2storage-rw",
+ "s2storage_tools",
+ "s2-geometry-library-java",
+ ],
+}
+
+// A tool to create a binary satellite S2 file.
+java_binary_host {
+ name: "satellite_createsats2file",
+ main_class: "com.android.telephony.tools.sats2.CreateSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// A tool to create a test satellite S2 file.
+java_binary_host {
+ name: "satellite_createsats2file_test",
+ main_class: "com.android.telephony.tools.sats2.CreateTestSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// A tool to dump a satellite S2 file as text for debugging.
+java_binary_host {
+ name: "satellite_dumpsats2file",
+ main_class: "com.android.telephony.tools.sats2.DumpSatS2File",
+ static_libs: [
+ "satellite-s2storage-tools",
+ ],
+}
+
+// Tests for CreateSatS2File.
+java_test_host {
+ name: "SatelliteToolsTests",
+ srcs: ["src/test/java/**/*.java"],
+ static_libs: [
+ "junit",
+ "satellite-s2storage-tools",
+ "s2-geometry-library-java",
+ "satellite-s2storage-testutils"
+ ],
+ test_suites: ["general-tests"],
+}
\ No newline at end of file
diff --git a/utils/satellite/tools/TEST_MAPPING b/utils/satellite/tools/TEST_MAPPING
new file mode 100644
index 0000000..df9511a
--- /dev/null
+++ b/utils/satellite/tools/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "SatelliteToolsTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java
new file mode 100644
index 0000000..f82cd5c
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateSatS2File.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+
+/** Creates a Sat S2 file from the list of S2 cells. */
+public final class CreateSatS2File {
+ /**
+ * Usage:
+ * CreateSatS2File <[input] s2 cells file> <[input] s2 level of input data>
+ * <[input] whether s2 cells is an allowed list> <[output] sat s2 file>
+ */
+ public static void main(String[] args) throws Exception {
+ Arguments arguments = new Arguments();
+ JCommander.newBuilder()
+ .addObject(arguments)
+ .build()
+ .parse(args);
+ String inputFile = arguments.inputFile;
+ int s2Level = arguments.s2Level;
+ String outputFile = arguments.outputFile;
+ boolean isAllowedList = Arguments.getBooleanValue(arguments.isAllowedList);
+ SatS2FileCreator.create(inputFile, s2Level, isAllowedList, outputFile);
+ }
+
+ private static class Arguments {
+ @Parameter(names = "--input-file",
+ description = "s2 cells file",
+ required = true)
+ public String inputFile;
+
+ @Parameter(names = "--s2-level",
+ description = "s2 level of input data",
+ required = true)
+ public int s2Level;
+
+ @Parameter(names = "--is-allowed-list",
+ description = "whether s2 cells file contains an allowed list of cells",
+ required = true)
+ public String isAllowedList;
+
+ @Parameter(names = "--output-file",
+ description = "sat s2 file",
+ required = true)
+ public String outputFile;
+
+ public static Boolean getBooleanValue(String value) {
+ if ("false".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value)) {
+ return Boolean.parseBoolean(value);
+ } else {
+ throw new ParameterException("Invalid boolean string:" + value);
+ }
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java
new file mode 100644
index 0000000..f9a9347
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/CreateTestSatS2File.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+/** Creates a Sat S2 file with a small amount of test data. Useful for testing other tools. */
+public final class CreateTestSatS2File {
+
+ /**
+ * Usage:
+ * CreateTestSatS2File <file name>
+ */
+ public static void main(String[] args) throws Exception {
+ File file = new File(args[0]);
+
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(12, true);
+ if (fileFormat.getPrefixBitCount() != 11) {
+ throw new IllegalStateException("Fake data requires 11 prefix bits");
+ }
+
+ try (SatS2RangeFileWriter satS2RangeFileWriter =
+ SatS2RangeFileWriter.open(file, fileFormat)) {
+ // Two ranges that share a prefix.
+ S2LevelRange range1 = new S2LevelRange(
+ fileFormat.createCellId(0b100_11111111, 1000),
+ fileFormat.createCellId(0b100_11111111, 2000));
+ S2LevelRange range2 = new S2LevelRange(
+ fileFormat.createCellId(0b100_11111111, 2000),
+ fileFormat.createCellId(0b100_11111111, 3000));
+ // This range has a different face, so a different prefix, and will be in a different
+ // suffix table.
+ S2LevelRange range3 = new S2LevelRange(
+ fileFormat.createCellId(0b101_11111111, 1000),
+ fileFormat.createCellId(0b101_11111111, 2000));
+ List<S2LevelRange> allRanges = listOf(range1, range2, range3);
+ satS2RangeFileWriter.createSortedSuffixBlocks(allRanges.iterator());
+ }
+ }
+
+ @SafeVarargs
+ private static <E> List<E> listOf(E... values) {
+ return Arrays.asList(values);
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java
new file mode 100644
index 0000000..2a9ce37
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/DumpSatS2File.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import com.android.storage.tools.block.DumpBlockFile;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.tools.sats2.dump.SatS2RangeFileDumper;
+
+import java.io.File;
+
+/**
+ * Dumps information about a Sat S2 data file. Like {@link DumpBlockFile} but it knows details about
+ * the Sat S2 format and can provide more detailed information.
+ */
+public final class DumpSatS2File {
+
+ /**
+ * Usage:
+ * DumpSatFile <[input] sat s2 file name> <[output] output directory name>
+ */
+ public static void main(String[] args) throws Exception {
+ String satS2FileName = args[0];
+ String outputDirName = args[1];
+
+ File outputDir = new File(outputDirName);
+ outputDir.mkdirs();
+
+ File satS2File = new File(satS2FileName);
+ try (SatS2RangeFileReader reader = SatS2RangeFileReader.open(satS2File)) {
+ reader.visit(new SatS2RangeFileDumper(outputDir));
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java
new file mode 100644
index 0000000..b800897
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/FileFormats.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+/** Some sample file formats. */
+public final class FileFormats {
+
+ // level 12: 27 S2 cell ID bits split 11 + 16,
+ // suffix table: 24 bits, 16/24 for cell id suffix, 8/24 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_12_ALLOWED_LIST =
+ new SatS2RangeFileFormat(12, 11, 16, 1, 24, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_12_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(12, 11, 16, 1, 24, false);
+
+ // level 14: 31 S2 cell ID bits split 13 + 18,
+ // suffix table: 32 bits, 18/32 for cell id suffix, 14/32 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_14_ALLOWED_LIST =
+ new SatS2RangeFileFormat(14, 13, 18, 1, 32, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_14_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(14, 13, 18, 1, 32, false);
+
+ // level 16: 35 S2 cell ID bits split 13 + 22,
+ // suffix table: 32 bits, 22/32 for cell id suffix, 10/32 dedicated to range
+ private static final SatS2RangeFileFormat FILE_FORMAT_16_ALLOWED_LIST =
+ new SatS2RangeFileFormat(16, 13, 22, 1, 32, true);
+ private static final SatS2RangeFileFormat FILE_FORMAT_16_DISALLOWED_LIST =
+ new SatS2RangeFileFormat(16, 13, 22, 1, 32, false);
+
+ /** Maps an S2 level to one of the file format constants declared on by class. */
+ public static SatS2RangeFileFormat getFileFormatForLevel(int s2Level, boolean isAllowedList) {
+ switch (s2Level) {
+ case 12:
+ return isAllowedList ? FILE_FORMAT_12_ALLOWED_LIST : FILE_FORMAT_12_DISALLOWED_LIST;
+ case 14:
+ return isAllowedList ? FILE_FORMAT_14_ALLOWED_LIST : FILE_FORMAT_14_DISALLOWED_LIST;
+ case 16:
+ return isAllowedList ? FILE_FORMAT_16_ALLOWED_LIST : FILE_FORMAT_16_DISALLOWED_LIST;
+ default:
+ throw new IllegalArgumentException("s2Level=" + s2Level
+ + ", isAllowedList=" + isAllowedList + " not mapped");
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java
new file mode 100644
index 0000000..b701a7b
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/SatS2FileCreator.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import com.android.storage.s2.S2LevelRange;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.write.SatS2RangeFileWriter;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.geometry.S2CellId;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/** A util class for creating a satellite S2 file from the list of S2 cells. */
+public final class SatS2FileCreator {
+ /**
+ * @param inputFile The input text file containing the list of S2 Cell IDs. Each line in the
+ * file contains a number in the range of a signed-64bit number which
+ * represents the ID of a S2 cell.
+ * @param s2Level The S2 level of all S2 cells in the input file.
+ * @param isAllowedList {@code true} means the input file contains an allowed list of S2 cells.
+ * {@code false} means the input file contains a disallowed list of S2
+ * cells.
+ * @param outputFile The output file to which the satellite S2 data in block format will be
+ * written.
+ */
+ public static void create(String inputFile, int s2Level, boolean isAllowedList,
+ String outputFile) throws Exception {
+ // Read a list of S2 cells from input file
+ List<Long> s2Cells = readS2CellsFromFile(inputFile);
+ System.out.println("Number of S2 cells read from file:" + s2Cells.size());
+
+ // Convert the input list of S2 Cells into the list of sorted S2CellId
+ List<S2CellId> sortedS2CellIds = s2Cells.stream()
+ .map(x -> new S2CellId(x))
+ .collect(Collectors.toList());
+ // IDs of S2CellId are converted to unsigned long numbers, which will be then used to
+ // compare S2CellId.
+ Collections.sort(sortedS2CellIds);
+
+ // Compress the list of S2CellId into S2 ranges
+ List<SatS2Range> satS2Ranges = createSatS2Ranges(sortedS2CellIds, s2Level);
+
+ // Write the S2 ranges into a block file
+ SatS2RangeFileFormat fileFormat =
+ FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ try (SatS2RangeFileWriter satS2RangeFileWriter =
+ SatS2RangeFileWriter.open(new File(outputFile), fileFormat)) {
+ Iterator<S2LevelRange> s2LevelRangeIterator = satS2Ranges
+ .stream()
+ .map(x -> new S2LevelRange(x.rangeStart.id(), x.rangeEnd.id()))
+ .iterator();
+ /*
+ * Group the sorted ranges into contiguous suffix blocks. Big ranges might get split as
+ * needed to fit them into suffix blocks.
+ */
+ satS2RangeFileWriter.createSortedSuffixBlocks(s2LevelRangeIterator);
+ }
+
+ // Validate the output block file
+ System.out.println("Validating the output block file...");
+ try (SatS2RangeFileReader satS2RangeFileReader =
+ SatS2RangeFileReader.open(new File(outputFile))) {
+ if (isAllowedList != satS2RangeFileReader.isAllowedList()) {
+ throw new IllegalStateException("isAllowedList="
+ + satS2RangeFileReader.isAllowedList() + " does not match the input "
+ + "argument=" + isAllowedList);
+ }
+
+ // Verify that all input S2 cells are present in the output block file
+ for (S2CellId s2CellId : sortedS2CellIds) {
+ if (satS2RangeFileReader.findEntryByCellId(s2CellId.id()) == null) {
+ throw new IllegalStateException("s2CellId=" + s2CellId
+ + " is not present in the output sat s2 file");
+ }
+ }
+
+ // Verify the cell right before the first cell in the sortedS2CellIds is not present in
+ // the output block file
+ S2CellId prevCell = sortedS2CellIds.get(0).prev();
+ if (!sortedS2CellIds.contains(prevCell)
+ && satS2RangeFileReader.findEntryByCellId(prevCell.id()) != null) {
+ throw new IllegalStateException("The cell " + prevCell + ", which is right "
+ + "before the first cell is unexpectedly present in the output sat s2"
+ + " file");
+ } else {
+ System.out.println("prevCell=" + prevCell + " is in the sortedS2CellIds");
+ }
+
+ // Verify the cell right after the last cell in the sortedS2CellIds is not present in
+ // the output block file
+ S2CellId nextCell = sortedS2CellIds.get(sortedS2CellIds.size() - 1).next();
+ if (!sortedS2CellIds.contains(nextCell)
+ && satS2RangeFileReader.findEntryByCellId(nextCell.id()) != null) {
+ throw new IllegalStateException("The cell " + nextCell + ", which is right "
+ + "after the last cell is unexpectedly present in the output sat s2"
+ + " file");
+ } else {
+ System.out.println("nextCell=" + nextCell + " is in the sortedS2CellIds");
+ }
+ }
+ System.out.println("Successfully validated the output block file");
+ }
+
+ /**
+ * Read a list of S2 cells from the inputFile.
+ *
+ * @param inputFile A file containing the list of S2 cells. Each line in the inputFile contains
+ * a long number - the ID of a S2 cell.
+ * @return A list of S2 cells.
+ */
+ private static List<Long> readS2CellsFromFile(String inputFile) throws Exception {
+ List<Long> s2Cells = new ArrayList();
+ InputStream inputStream = new FileInputStream(inputFile);
+ try (Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name())) {
+ while (scanner.hasNextLong()) {
+ s2Cells.add(scanner.nextLong());
+ }
+ if (scanner.hasNextLine()) {
+ throw new IllegalStateException("Input s2 cell file has invalid format, "
+ + "current line=" + scanner.nextLine());
+ }
+ }
+ return s2Cells;
+ }
+
+ /**
+ * Compress the list of sorted S2CellId into S2 ranges.
+ *
+ * @param sortedS2CellIds List of S2CellId sorted in ascending order.
+ * @param s2Level The level of all S2CellId.
+ * @return List of S2 ranges.
+ */
+ private static List<SatS2Range> createSatS2Ranges(List<S2CellId> sortedS2CellIds, int s2Level) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ List<SatS2Range> ranges = new ArrayList<>();
+ if (sortedS2CellIds != null && sortedS2CellIds.size() > 0) {
+ S2CellId rangeStart = null;
+ S2CellId rangeEnd = null;
+ for (int i = 0; i < sortedS2CellIds.size(); i++) {
+ S2CellId currentS2CellId = sortedS2CellIds.get(i);
+ checkCellIdIsAtLevel(currentS2CellId, s2Level);
+
+ SatS2Range currentRange = createS2Range(currentS2CellId, s2Level);
+ S2CellId currentS2CellRangeStart = currentRange.rangeStart;
+ S2CellId currentS2CellRangeEnd = currentRange.rangeEnd;
+
+ if (rangeStart == null) {
+ // First time round the loop initialize rangeStart / rangeEnd only.
+ rangeStart = currentS2CellRangeStart;
+ } else if (rangeEnd.id() != currentS2CellRangeStart.id()) {
+ // If there's a gap between cellIds, store the range we have so far and start a
+ // new range.
+ ranges.add(new SatS2Range(rangeStart, rangeEnd));
+ rangeStart = currentS2CellRangeStart;
+ }
+ rangeEnd = currentS2CellRangeEnd;
+ }
+ ranges.add(new SatS2Range(rangeStart, rangeEnd));
+ }
+
+ // Sorting the ranges is not necessary. As the input is sorted , it will already be sorted.
+ System.out.printf("Created %s SatS2Ranges in %s milliseconds\n",
+ ranges.size(), stopwatch.elapsed(TimeUnit.MILLISECONDS));
+ return ranges;
+ }
+
+ /**
+ * @return A pair of S2CellId for the range [s2CellId, s2CellId's next sibling)
+ */
+ private static SatS2Range createS2Range(
+ S2CellId s2CellId, int s2Level) {
+ // Since s2CellId is at s2Level, s2CellId.childBegin(s2Level) returns itself.
+ S2CellId firstS2CellRangeStart = s2CellId.childBegin(s2Level);
+ // Get the immediate next sibling of s2CellId
+ S2CellId firstS2CellRangeEnd = s2CellId.childEnd(s2Level);
+
+ if (firstS2CellRangeEnd.face() < firstS2CellRangeStart.face()
+ || !firstS2CellRangeEnd.isValid()) {
+ // Fix this if it becomes an issue.
+ throw new IllegalStateException("firstS2CellId=" + s2CellId
+ + ", childEnd(" + s2Level + ") produced an unsupported"
+ + " value=" + firstS2CellRangeEnd);
+ }
+ return new SatS2Range(firstS2CellRangeStart, firstS2CellRangeEnd);
+ }
+
+ private static void checkCellIdIsAtLevel(S2CellId cellId, int s2Level) {
+ if (cellId.level() != s2Level) {
+ throw new IllegalStateException("Bad level for cellId=" + cellId
+ + ". Must be s2Level=" + s2Level);
+ }
+ }
+
+ /**
+ * A range of S2 cell IDs at a fixed S2 level. The range is expressed as a start cell ID
+ * (inclusive) and an end cell ID (exclusive).
+ */
+ private static class SatS2Range {
+ public final S2CellId rangeStart;
+ public final S2CellId rangeEnd;
+
+ /**
+ * Creates an instance. If the range is invalid or the cell IDs are from different levels
+ * this method throws an {@link IllegalArgumentException}.
+ */
+ SatS2Range(S2CellId rangeStart, S2CellId rangeEnd) {
+ this.rangeStart = Objects.requireNonNull(rangeStart);
+ this.rangeEnd = Objects.requireNonNull(rangeEnd);
+ if (rangeStart.level() != rangeEnd.level()) {
+ throw new IllegalArgumentException(
+ "Levels differ: rangeStart=" + rangeStart + ", rangeEnd=" + rangeEnd);
+ }
+ if (rangeStart.greaterOrEquals(rangeEnd)) {
+ throw new IllegalArgumentException(
+ "Range start (" + rangeStart + " >= range end (" + rangeEnd + ")");
+ }
+ }
+ }
+}
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java
new file mode 100644
index 0000000..69a3f70
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/HeaderBlockDumper.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2.dump;
+
+import com.android.storage.tools.block.dump.SingleFileDumper;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+
+import java.io.File;
+
+/** A {@link HeaderBlock.HeaderBlockVisitor} that dumps information to a file. */
+final class HeaderBlockDumper extends SingleFileDumper implements HeaderBlock.HeaderBlockVisitor {
+
+ HeaderBlockDumper(File headerBlockFile) {
+ super(headerBlockFile);
+ }
+
+ @Override
+ public void visitFileFormat(SatS2RangeFileFormat fileFormat) {
+ println("File format");
+ println("===========");
+ println(fileFormat.toString());
+ println();
+ }
+}
+
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java
new file mode 100644
index 0000000..307275a
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SatS2RangeFileDumper.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2.dump;
+
+import static com.android.storage.tools.block.dump.DumpUtils.binaryStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.hexStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadBinary;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadHex;
+
+import com.android.storage.tools.block.dump.SingleFileDumper;
+import com.android.telephony.sats2range.read.HeaderBlock;
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+import com.android.telephony.sats2range.read.SuffixTableExtraInfo;
+
+import java.io.File;
+
+/** A {@link SatS2RangeFileReader.SatS2RangeFileVisitor} that dumps information to a file. */
+public final class SatS2RangeFileDumper implements SatS2RangeFileReader.SatS2RangeFileVisitor {
+
+ private final File mOutputDir;
+
+ private int mMaxPrefix;
+
+ private int mMaxPrefixBinaryLength;
+
+ private int mMaxPrefixHexLength;
+
+ private SingleFileDumper mExtraInfoDumper;
+
+ public SatS2RangeFileDumper(File outputDir) {
+ mOutputDir = outputDir;
+ }
+
+ @Override
+ public void begin() throws VisitException {
+ mExtraInfoDumper = new SingleFileDumper(new File(mOutputDir, "suffixtable_extrainfo.txt"));
+ mExtraInfoDumper.begin();
+ }
+
+ @Override
+ public void visitSuffixTableExtraInfo(SuffixTableExtraInfo suffixTableExtraInfo) {
+ int prefix = suffixTableExtraInfo.getPrefix();
+ mExtraInfoDumper.println("prefix=" + zeroPadBinary(mMaxPrefixBinaryLength, prefix)
+ + "(" + zeroPadHex(mMaxPrefixHexLength, prefix) + ")"
+ + ", entryCount=" + suffixTableExtraInfo.getEntryCount());
+ }
+
+ @Override
+ public void visitHeaderBlock(HeaderBlock headerBlock) throws VisitException {
+ File headerFile = new File(mOutputDir, "header.txt");
+ headerBlock.visit(new HeaderBlockDumper(headerFile));
+ SatS2RangeFileFormat fileFormat = headerBlock.getFileFormat();
+ mMaxPrefix = fileFormat.getMaxPrefixValue();
+ mMaxPrefixBinaryLength = binaryStringLength(mMaxPrefix);
+ mMaxPrefixHexLength = hexStringLength(mMaxPrefix);
+ }
+
+ @Override
+ public void visitSuffixTableBlock(SuffixTableBlock suffixTableBlock)
+ throws VisitException {
+ suffixTableBlock.visit(new SuffixTableBlockDumper(mOutputDir, mMaxPrefix));
+ }
+
+ @Override
+ public void end() throws VisitException {
+ mExtraInfoDumper.end();
+ }
+}
+
diff --git a/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java
new file mode 100644
index 0000000..a5d75b4
--- /dev/null
+++ b/utils/satellite/tools/src/main/java/com/android/telephony/tools/sats2/dump/SuffixTableBlockDumper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2.dump;
+
+import static com.android.storage.tools.block.dump.DumpUtils.binaryStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.createPrintWriter;
+import static com.android.storage.tools.block.dump.DumpUtils.generateDumpFile;
+import static com.android.storage.tools.block.dump.DumpUtils.hexStringLength;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadBinary;
+import static com.android.storage.tools.block.dump.DumpUtils.zeroPadHex;
+
+import com.android.telephony.sats2range.read.SuffixTableBlock;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Objects;
+
+/** A {@link SuffixTableBlock.SuffixTableBlockVisitor} that dumps information to a file. */
+public final class SuffixTableBlockDumper implements SuffixTableBlock.SuffixTableBlockVisitor {
+
+ private final File mOutputDir;
+
+ private final int mMaxPrefix;
+
+ public SuffixTableBlockDumper(File outputDir, int maxPrefix) {
+ mOutputDir = Objects.requireNonNull(outputDir);
+ mMaxPrefix = maxPrefix;
+ }
+
+ @Override
+ public void visit(SuffixTableBlock suffixTableBlock) throws VisitException {
+ int tablePrefix = suffixTableBlock.getPrefix();
+ int prefixHexLength = hexStringLength(tablePrefix);
+ int prefixBinaryLength = binaryStringLength(tablePrefix);
+ File suffixTableFile =
+ generateDumpFile(mOutputDir, "suffixtable_", tablePrefix, mMaxPrefix);
+ try (PrintWriter writer = createPrintWriter(suffixTableFile)) {
+ writer.println("Prefix value=" + zeroPadBinary(prefixBinaryLength, tablePrefix)
+ + " (" + zeroPadHex(prefixHexLength, tablePrefix) + ")");
+ int entryCount = suffixTableBlock.getEntryCount();
+ writer.println("Entry count=" + entryCount);
+ if (entryCount > 0) {
+ for (int i = 0; i < entryCount; i++) {
+ writer.println(
+ "[" + i + "]=" + suffixTableBlock.getEntryByIndex(i)
+ .getSuffixTableRange());
+ }
+ }
+ }
+ }
+}
+
diff --git a/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java b/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java
new file mode 100644
index 0000000..80c1807
--- /dev/null
+++ b/utils/satellite/tools/src/test/java/com/android/telephony/tools/sats2/CreateSatS2FileTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 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 com.android.telephony.tools.sats2;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import com.android.telephony.sats2range.read.SatS2RangeFileFormat;
+import com.android.telephony.sats2range.read.SatS2RangeFileReader;
+import com.android.telephony.sats2range.utils.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+
+/** Tests for {@link CreateSatS2File} */
+public final class CreateSatS2FileTest {
+ private Path mTempDirPath;
+
+ @Before
+ public void setUp() throws IOException {
+ mTempDirPath = TestUtils.createTempDir(this.getClass());
+ }
+
+ @After
+ public void tearDown() throws IOException {
+ if (mTempDirPath != null) {
+ TestUtils.deleteDirectory(mTempDirPath);
+ }
+ }
+
+ @Test
+ public void testCreateSatS2FileWithValidInput_AllowedList() throws Exception {
+ testCreateSatS2FileWithValidInput(true);
+ }
+
+ @Test
+ public void testCreateSatS2FileWithValidInput_DisallowedList() throws Exception {
+ testCreateSatS2FileWithValidInput(false);
+ }
+
+ @Test
+ public void testCreateSatS2FileWithInvalidInput() throws Exception {
+ int s2Level = 12;
+ boolean isAllowedList = true;
+ Path inputDirPath = mTempDirPath.resolve("input");
+ Files.createDirectory(inputDirPath);
+ Path inputFilePath = inputDirPath.resolve("s2cells.txt");
+
+ Path outputDirPath = mTempDirPath.resolve("output");
+ Files.createDirectory(outputDirPath);
+ Path outputFilePath = outputDirPath.resolve("sats2.dat");
+
+ // Create test input S2 cell file
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ TestUtils.createInvalidTestS2CellFile(inputFilePath.toFile(), fileFormat);
+
+ // Commandline input arguments
+ String[] args = {
+ "--input-file", inputFilePath.toAbsolutePath().toString(),
+ "--s2-level", String.valueOf(s2Level),
+ "--is-allowed-list", isAllowedList ? "true" : "false",
+ "--output-file", outputFilePath.toAbsolutePath().toString()
+ };
+
+ // Execute the tool CreateSatS2File and expect exception
+ try {
+ CreateSatS2File.main(args);
+ } catch (Exception ex) {
+ // Expected exception
+ return;
+ }
+ fail("Exception should have been caught");
+ }
+
+ private void testCreateSatS2FileWithValidInput(boolean isAllowedList) throws Exception {
+ int s2Level = 12;
+ Path inputDirPath = mTempDirPath.resolve("input");
+ Files.createDirectory(inputDirPath);
+ Path inputFilePath = inputDirPath.resolve("s2cells.txt");
+
+ Path outputDirPath = mTempDirPath.resolve("output");
+ Files.createDirectory(outputDirPath);
+ Path outputFilePath = outputDirPath.resolve("sats2.dat");
+
+ /*
+ * Create test input S2 cell file with the following ranges:
+ * 1) [(prefix=0b100_11111111, suffix=1000), (prefix=0b100_11111111, suffix=2000))
+ * 2) [(prefix=0b100_11111111, suffix=2001), (prefix=0b100_11111111, suffix=3000))
+ * 3) [(prefix=0b101_11111111, suffix=1000), (prefix=0b101_11111111, suffix=2001))
+ */
+ SatS2RangeFileFormat fileFormat = FileFormats.getFileFormatForLevel(s2Level, isAllowedList);
+ TestUtils.createValidTestS2CellFile(inputFilePath.toFile(), fileFormat);
+
+ // Commandline input arguments
+ String[] args = {
+ "--input-file", inputFilePath.toAbsolutePath().toString(),
+ "--s2-level", String.valueOf(s2Level),
+ "--is-allowed-list", isAllowedList ? "true" : "false",
+ "--output-file", outputFilePath.toAbsolutePath().toString()
+ };
+
+ // Execute the tool CreateSatS2File and expect successful result
+ try {
+ CreateSatS2File.main(args);
+ } catch (Exception ex) {
+ fail("Unexpected exception when executing the tool ex=" + ex);
+ }
+
+ // Validate the output block file
+ try {
+ SatS2RangeFileReader satS2RangeFileReader =
+ SatS2RangeFileReader.open(outputFilePath.toFile());
+ if (isAllowedList != satS2RangeFileReader.isAllowedList()) {
+ fail("isAllowedList="
+ + satS2RangeFileReader.isAllowedList() + " does not match the input "
+ + "argument=" + isAllowedList);
+ }
+
+ // Verify an edge cell (prefix=0b100_11111111, suffix=100)
+ long s2CellId = fileFormat.createCellId(0b100_11111111, 100);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify a middle cell (prefix=0b100_11111111, suffix=2000)
+ s2CellId = fileFormat.createCellId(0b100_11111111, 2000);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b100_11111111, suffix=4000)
+ s2CellId = fileFormat.createCellId(0b100_11111111, 4000);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=500)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=2001)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 2500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+
+ // Verify an edge cell (prefix=0b101_11111111, suffix=2500)
+ s2CellId = fileFormat.createCellId(0b101_11111111, 2500);
+ assertNull(satS2RangeFileReader.findEntryByCellId(s2CellId));
+ } catch (Exception ex) {
+ fail("Unexpected exception when validating the output ex=" + ex);
+ }
+ }
+}