Merge "Enable reuse of LinearBlock for during secure playback" into main
diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp
index 65ec4d4..3e277e8 100644
--- a/api/StubLibraries.bp
+++ b/api/StubLibraries.bp
@@ -54,34 +54,34 @@
baseline_file: ":non-updatable-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/public/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/public/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -134,34 +134,34 @@
baseline_file: ":non-updatable-system-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -189,56 +189,58 @@
baseline_file: ":non-updatable-test-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "removed.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/test/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
tag: ".exportable.removed-api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "removed.txt",
tag: ".removed-api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/test/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
@@ -271,34 +273,34 @@
baseline_file: ":non-updatable-module-lib-lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/module-lib/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/module-lib/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt
index 5178f09..5efda98 100644
--- a/api/coverage/tools/ExtractFlaggedApis.kt
+++ b/api/coverage/tools/ExtractFlaggedApis.kt
@@ -17,6 +17,7 @@
package android.platform.coverage
import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Item
import com.android.tools.metalava.model.MethodItem
import com.android.tools.metalava.model.text.ApiFile
import java.io.File
@@ -44,20 +45,9 @@
builder: FlagApiMap.Builder
) {
if (methods.isEmpty()) return
- val classFlag =
- classItem.modifiers
- .findAnnotation("android.annotation.FlaggedApi")
- ?.findAttribute("value")
- ?.value
- ?.value() as? String
+ val classFlag = getClassFlag(classItem)
for (method in methods) {
- val methodFlag =
- method.modifiers
- .findAnnotation("android.annotation.FlaggedApi")
- ?.findAttribute("value")
- ?.value
- ?.value() as? String
- ?: classFlag
+ val methodFlag = getFlagAnnotation(method) ?: classFlag
val api =
JavaMethod.newBuilder()
.setPackageName(packageName)
@@ -81,3 +71,23 @@
builder.putFlagToApi(flag, apis)
}
}
+
+fun getClassFlag(classItem: ClassItem): String? {
+ var classFlag = getFlagAnnotation(classItem)
+ var cur = classItem
+ // If a class is not an inner class, use its @FlaggedApi annotation value.
+ // Otherwise, use the flag value of the closest outer class that is annotated by @FlaggedApi.
+ while (cur.isInnerClass() && classFlag == null) {
+ cur = cur.parent() as ClassItem
+ classFlag = getFlagAnnotation(cur)
+ }
+ return classFlag
+}
+
+fun getFlagAnnotation(item: Item): String? {
+ return item.modifiers
+ .findAnnotation("android.annotation.FlaggedApi")
+ ?.findAttribute("value")
+ ?.value
+ ?.value() as? String
+}
diff --git a/api/coverage/tools/ExtractFlaggedApisTest.kt b/api/coverage/tools/ExtractFlaggedApisTest.kt
index ee5aaf1..427be36 100644
--- a/api/coverage/tools/ExtractFlaggedApisTest.kt
+++ b/api/coverage/tools/ExtractFlaggedApisTest.kt
@@ -141,6 +141,84 @@
assertThat(result).isEqualTo(expected.build())
}
+ @Test
+ fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.location.provider {
+ @FlaggedApi(Flags.FLAG_NEW_GEOCODER) public final class ForwardGeocodeRequest implements android.os.Parcelable {
+ method public int describeContents();
+ }
+ public static final class ForwardGeocodeRequest.Builder {
+ method @NonNull public android.location.provider.ForwardGeocodeRequest build();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api1 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.location.provider")
+ .setClassName("ForwardGeocodeRequest")
+ .setMethodName("describeContents")
+ addFlaggedApi(expected, api1, "Flags.FLAG_NEW_GEOCODER")
+ val api2 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.location.provider")
+ .setClassName("ForwardGeocodeRequest.Builder")
+ .setMethodName("build")
+ addFlaggedApi(expected, api2, "Flags.FLAG_NEW_GEOCODER")
+ assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
+ }
+
+ @Test
+ fun extractFlaggedApis_unflaggedNestedClassShouldUseOuterClassFlag_deeplyNested() {
+ val apiText =
+ """
+ // Signature format: 2.0
+ package android.package.xyz {
+ @FlaggedApi(outer_class_flag) public final class OuterClass {
+ method public int apiInOuterClass();
+ }
+ public final class OuterClass.Deeply.NestedClass {
+ method public void apiInNestedClass();
+ }
+ }
+ """
+ .trimIndent()
+ Files.write(apiTextFile, apiText.toByteArray(Charsets.UTF_8), StandardOpenOption.APPEND)
+
+ val process = Runtime.getRuntime().exec(createCommand())
+ process.waitFor()
+
+ val content = Files.readAllBytes(flagToApiMap).toString(Charsets.UTF_8)
+ val result = TextFormat.parse(content, FlagApiMap::class.java)
+
+ val expected = FlagApiMap.newBuilder()
+ val api1 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.package.xyz")
+ .setClassName("OuterClass")
+ .setMethodName("apiInOuterClass")
+ addFlaggedApi(expected, api1, "outer_class_flag")
+ val api2 =
+ JavaMethod.newBuilder()
+ .setPackageName("android.package.xyz")
+ .setClassName("OuterClass.Deeply.NestedClass")
+ .setMethodName("apiInNestedClass")
+ addFlaggedApi(expected, api2, "outer_class_flag")
+ assertThat(result).ignoringRepeatedFieldOrder().isEqualTo(expected.build())
+ }
+
private fun addFlaggedApi(builder: FlagApiMap.Builder, api: JavaMethod.Builder, flag: String) {
if (builder.containsFlagToApi(flag)) {
val updatedApis =
diff --git a/core/java/android/ddm/DdmHandleHello.java b/core/java/android/ddm/DdmHandleHello.java
index a51a740..d9a18d7 100644
--- a/core/java/android/ddm/DdmHandleHello.java
+++ b/core/java/android/ddm/DdmHandleHello.java
@@ -42,12 +42,6 @@
private static DdmHandleHello mInstance = new DdmHandleHello();
- private static final String[] FRAMEWORK_FEATURES = new String[] {
- "opengl-tracing",
- "view-hierarchy",
- "support_boot_stages"
- };
-
/* singleton, do not instantiate */
private DdmHandleHello() {}
@@ -193,22 +187,25 @@
if (false)
Log.v("ddm-heap", "Got feature list request");
- int size = 4 + 4 * (vmFeatures.length + FRAMEWORK_FEATURES.length);
- for (int i = vmFeatures.length-1; i >= 0; i--)
+ String[] fmFeatures = Debug.getFeatureList();
+ int size = 4 + 4 * (vmFeatures.length + fmFeatures.length);
+ for (int i = vmFeatures.length - 1; i >= 0; i--) {
size += vmFeatures[i].length() * 2;
- for (int i = FRAMEWORK_FEATURES.length-1; i>= 0; i--)
- size += FRAMEWORK_FEATURES[i].length() * 2;
+ }
+ for (int i = fmFeatures.length - 1; i >= 0; i--) {
+ size += fmFeatures[i].length() * 2;
+ }
ByteBuffer out = ByteBuffer.allocate(size);
out.order(ChunkHandler.CHUNK_ORDER);
- out.putInt(vmFeatures.length + FRAMEWORK_FEATURES.length);
+ out.putInt(vmFeatures.length + fmFeatures.length);
for (int i = vmFeatures.length-1; i >= 0; i--) {
out.putInt(vmFeatures[i].length());
putString(out, vmFeatures[i]);
}
- for (int i = FRAMEWORK_FEATURES.length-1; i >= 0; i--) {
- out.putInt(FRAMEWORK_FEATURES[i].length());
- putString(out, FRAMEWORK_FEATURES[i]);
+ for (int i = fmFeatures.length - 1; i >= 0; i--) {
+ out.putInt(fmFeatures[i].length());
+ putString(out, fmFeatures[i]);
}
return new Chunk(CHUNK_FEAT, out);
diff --git a/core/java/android/net/vcn/VcnManager.java b/core/java/android/net/vcn/VcnManager.java
index 6246dd7..91cdf8d 100644
--- a/core/java/android/net/vcn/VcnManager.java
+++ b/core/java/android/net/vcn/VcnManager.java
@@ -124,6 +124,22 @@
"vcn_network_selection_ipsec_packet_loss_percent_threshold";
/**
+ * Key for detecting unusually large increases in IPsec packet sequence numbers.
+ *
+ * <p>If the sequence number increases by more than this value within a second, it may indicate
+ * an intentional leap on the server's downlink. To avoid false positives, the packet loss
+ * detector will suppress loss reporting.
+ *
+ * <p>By default, there's no maximum limit enforced, prioritizing detection of lossy networks.
+ * To reduce false positives, consider setting an appropriate maximum threshold.
+ *
+ * @hide
+ */
+ @NonNull
+ public static final String VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY =
+ "vcn_network_selection_max_seq_num_increase_per_second";
+
+ /**
* Key for the list of timeouts in minute to stop penalizing an underlying network candidate
*
* @hide
@@ -180,6 +196,7 @@
VCN_NETWORK_SELECTION_WIFI_EXIT_RSSI_THRESHOLD_KEY,
VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY,
VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY,
+ VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
VCN_NETWORK_SELECTION_PENALTY_TIMEOUT_MINUTES_LIST_KEY,
VCN_RESTRICTED_TRANSPORTS_INT_ARRAY_KEY,
VCN_SAFE_MODE_TIMEOUT_SECONDS_KEY,
diff --git a/core/java/android/net/vcn/flags.aconfig b/core/java/android/net/vcn/flags.aconfig
index e64823a..6fde398 100644
--- a/core/java/android/net/vcn/flags.aconfig
+++ b/core/java/android/net/vcn/flags.aconfig
@@ -34,4 +34,14 @@
namespace: "vcn"
description: "Re-evaluate IPsec packet loss on LinkProperties or NetworkCapabilities change"
bug: "323238888"
+}
+
+flag{
+ name: "handle_seq_num_leap"
+ namespace: "vcn"
+ description: "Do not report bad network when there is a suspected sequence number leap"
+ bug: "332598276"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
\ No newline at end of file
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index f785cca..a55398a 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -110,6 +110,12 @@
private static final String DEFAULT_TRACE_BODY = "dmtrace";
private static final String DEFAULT_TRACE_EXTENSION = ".trace";
+ private static final String[] FRAMEWORK_FEATURES = new String[] {
+ "opengl-tracing",
+ "view-hierarchy",
+ "support_boot_stages",
+ };
+
/**
* This class is used to retrieved various statistics about the memory mappings for this
* process. The returned info is broken down by dalvik, native, and other. All results are in kB.
@@ -1106,6 +1112,17 @@
}
/**
+ * Returns an array of strings that identify Framework features. This is
+ * used by DDMS to determine what sorts of operations the Framework can
+ * perform.
+ *
+ * @hide
+ */
+ public static String[] getFeatureList() {
+ return FRAMEWORK_FEATURES;
+ }
+
+ /**
* Change the JDWP port.
*
* @deprecated no longer needed or useful
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 8c6bf79..6412ddb 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -21,6 +21,7 @@
config_namespace: "ANDROID",
bool_variables: [
"release_binder_death_recipient_weak_from_jni",
+ "release_package_libandroid_runtime_punch_holes",
],
properties: [
"cflags",
@@ -63,6 +64,9 @@
release_binder_death_recipient_weak_from_jni: {
cflags: ["-DBINDER_DEATH_RECIPIENT_WEAK_FROM_JNI"],
},
+ release_package_libandroid_runtime_punch_holes: {
+ cflags: ["-DENABLE_PUNCH_HOLES"],
+ },
},
cpp_std: "gnu++20",
@@ -120,6 +124,7 @@
srcs: [
"AndroidRuntime.cpp",
"com_android_internal_content_F2fsUtils.cpp",
+ "com_android_internal_content_FileSystemUtils.cpp",
"com_android_internal_content_NativeLibraryHelper.cpp",
"com_google_android_gles_jni_EGLImpl.cpp",
"com_google_android_gles_jni_GLImpl.cpp", // TODO: .arm
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
new file mode 100644
index 0000000..01920de
--- /dev/null
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -0,0 +1,329 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "FileSystemUtils"
+
+#include "com_android_internal_content_FileSystemUtils.h"
+
+#include <android-base/file.h>
+#include <android-base/hex.h>
+#include <android-base/unique_fd.h>
+#include <elf.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <utils/Log.h>
+
+#include <array>
+#include <fstream>
+#include <vector>
+
+using android::base::HexString;
+using android::base::ReadFullyAtOffset;
+
+namespace android {
+bool punchHoles(const char *filePath, const uint64_t offset,
+ const std::vector<Elf64_Phdr> &programHeaders) {
+ struct stat64 beforePunch;
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
+ uint64_t blockSize = beforePunch.st_blksize;
+ IF_ALOGD() {
+ ALOGD("Total number of LOAD segments %zu", programHeaders.size());
+
+ ALOGD("Size before punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ beforePunch.st_blocks, beforePunch.st_blksize,
+ static_cast<uint64_t>(beforePunch.st_size));
+ }
+
+ android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
+ ALOGE("Can't open file to punch %s", filePath);
+ return false;
+ }
+
+ // read in chunks of 64KB
+ constexpr uint64_t kChunkSize = 64 * 1024;
+
+ // malloc is used to gracefully handle oom which might occur during the allocation of buffer.
+ // allocating using new or vector here results in oom/exception on failure where as malloc will
+ // return nullptr.
+ std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kChunkSize)),
+ &free);
+ if (buffer == nullptr) {
+ ALOGE("Failed to allocate read buffer");
+ return false;
+ }
+
+ for (size_t index = 0; programHeaders.size() >= 2 && index < programHeaders.size() - 1;
+ index++) {
+ // find LOAD segments from program headers, calculate padding and punch holes
+ uint64_t punchOffset;
+ if (__builtin_add_overflow(programHeaders[index].p_offset, programHeaders[index].p_filesz,
+ &punchOffset)) {
+ ALOGE("Overflow occurred when adding offset and filesize");
+ return false;
+ }
+
+ uint64_t punchLen;
+ if (__builtin_sub_overflow(programHeaders[index + 1].p_offset, punchOffset, &punchLen)) {
+ ALOGE("Overflow occurred when calculating length");
+ return false;
+ }
+
+ if (punchLen < blockSize) {
+ continue;
+ }
+
+ uint64_t punchStartOffset;
+ if (__builtin_add_overflow(offset, punchOffset, &punchStartOffset)) {
+ ALOGE("Overflow occurred when calculating length");
+ return false;
+ }
+
+ uint64_t position = punchStartOffset;
+ uint64_t endPosition;
+ if (__builtin_add_overflow(position, punchLen, &endPosition)) {
+ ALOGE("Overflow occurred when calculating length");
+ return false;
+ }
+
+ // Read content in kChunkSize and verify it is zero
+ while (position <= endPosition) {
+ uint64_t uncheckedChunkEnd;
+ if (__builtin_add_overflow(position, kChunkSize, &uncheckedChunkEnd)) {
+ ALOGE("Overflow occurred when calculating uncheckedChunkEnd");
+ return false;
+ }
+
+ uint64_t readLength;
+ if (__builtin_sub_overflow(std::min(uncheckedChunkEnd, endPosition), position,
+ &readLength)) {
+ ALOGE("Overflow occurred when calculating readLength");
+ return false;
+ }
+
+ if (!ReadFullyAtOffset(fd, buffer.get(), readLength, position)) {
+ ALOGE("Failed to read content to punch holes");
+ return false;
+ }
+
+ IF_ALOGD() {
+ ALOGD("Punching holes for length:%" PRIu64 " content which should be zero: %s",
+ readLength, HexString(buffer.get(), readLength).c_str());
+ }
+
+ bool isZero = std::all_of(buffer.get(), buffer.get() + readLength,
+ [](uint8_t i) constexpr { return i == 0; });
+ if (!isZero) {
+ ALOGE("Found non zero content while trying to punch hole. Skipping operation");
+ return false;
+ }
+
+ position = uncheckedChunkEnd;
+ }
+
+ // if we have a uncompressed file which is being opened from APK, use the offset to
+ // punch native lib inside Apk.
+ int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchStartOffset,
+ punchLen);
+ if (result < 0) {
+ ALOGE("fallocate failed to punch hole, error:%d", errno);
+ return false;
+ }
+ }
+
+ IF_ALOGD() {
+ struct stat64 afterPunch;
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+ ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
+ "",
+ afterPunch.st_blocks, afterPunch.st_blksize,
+ static_cast<uint64_t>(afterPunch.st_size));
+ }
+
+ return true;
+}
+
+bool punchHolesInElf64(const char *filePath, const uint64_t offset) {
+ // Open Elf file
+ Elf64_Ehdr ehdr;
+ std::ifstream inputStream(filePath, std::ifstream::in);
+
+ // If this is a zip file, set the offset so that we can read elf file directly
+ inputStream.seekg(offset);
+ // read executable headers
+ inputStream.read((char *)&ehdr, sizeof(ehdr));
+ if (!inputStream.good()) {
+ return false;
+ }
+
+ // only consider elf64 for punching holes
+ if (ehdr.e_ident[EI_CLASS] != ELFCLASS64) {
+ ALOGW("Provided file is not ELF64");
+ return false;
+ }
+
+ // read the program headers from elf file
+ uint64_t programHeaderOffset = ehdr.e_phoff;
+ uint16_t programHeaderNum = ehdr.e_phnum;
+
+ IF_ALOGD() {
+ ALOGD("Punching holes in file: %s programHeaderOffset: %" PRIu64 " programHeaderNum: %hu",
+ filePath, programHeaderOffset, programHeaderNum);
+ }
+
+ // if this is a zip file, also consider elf offset inside a file
+ uint64_t phOffset;
+ if (__builtin_add_overflow(offset, programHeaderOffset, &phOffset)) {
+ ALOGE("Overflow occurred when calculating phOffset");
+ return false;
+ }
+ inputStream.seekg(phOffset);
+
+ std::vector<Elf64_Phdr> programHeaders;
+ for (int headerIndex = 0; headerIndex < programHeaderNum; headerIndex++) {
+ Elf64_Phdr header;
+ inputStream.read((char *)&header, sizeof(header));
+ if (!inputStream.good()) {
+ return false;
+ }
+
+ if (header.p_type != PT_LOAD) {
+ continue;
+ }
+ programHeaders.push_back(header);
+ }
+
+ return punchHoles(filePath, offset, programHeaders);
+}
+
+bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldLen) {
+ android::base::unique_fd fd(open(filePath, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
+ ALOGE("Can't open file to punch %s", filePath);
+ return false;
+ }
+
+ struct stat64 beforePunch;
+ if (int result = lstat64(filePath, &beforePunch); result != 0) {
+ ALOGE("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+
+ uint64_t blockSize = beforePunch.st_blksize;
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
+ static_cast<uint64_t>(beforePunch.st_size));
+ }
+
+ if (extraFieldLen < blockSize) {
+ ALOGD("Skipping punching apk as extra field length is less than block size");
+ return false;
+ }
+
+ // content is preceded by extra field. Zip offset is offset of exact content.
+ // move back by extraFieldLen so that scan can be started at start of extra field.
+ uint64_t extraFieldStart;
+ if (__builtin_sub_overflow(offset, extraFieldLen, &extraFieldStart)) {
+ ALOGE("Overflow occurred when calculating start of extra field");
+ return false;
+ }
+
+ constexpr uint64_t kMaxSize = 64 * 1024;
+ // Use malloc to gracefully handle any oom conditions
+ std::unique_ptr<uint8_t, decltype(&free)> buffer(static_cast<uint8_t *>(malloc(kMaxSize)),
+ &free);
+ if (buffer == nullptr) {
+ ALOGE("Failed to allocate read buffer");
+ return false;
+ }
+
+ // Read the entire extra fields at once and punch file according to zero stretches.
+ if (!ReadFullyAtOffset(fd, buffer.get(), extraFieldLen, extraFieldStart)) {
+ ALOGE("Failed to read extra field content");
+ return false;
+ }
+
+ IF_ALOGD() {
+ ALOGD("Extra field length: %hu content near offset: %s", extraFieldLen,
+ HexString(buffer.get(), extraFieldLen).c_str());
+ }
+
+ uint64_t currentSize = 0;
+ while (currentSize < extraFieldLen) {
+ uint64_t end = currentSize;
+ // find zero ranges
+ while (end < extraFieldLen && *(buffer.get() + end) == 0) {
+ ++end;
+ }
+
+ uint64_t punchLen;
+ if (__builtin_sub_overflow(end, currentSize, &punchLen)) {
+ ALOGW("Overflow occurred when calculating punching length");
+ return false;
+ }
+
+ // Don't punch for every stretch of zero which is found
+ if (punchLen > blockSize) {
+ uint64_t punchOffset;
+ if (__builtin_add_overflow(extraFieldStart, currentSize, &punchOffset)) {
+ ALOGW("Overflow occurred when calculating punch start offset");
+ return false;
+ }
+
+ ALOGD("Punching hole in apk start: %" PRIu64 " len:%" PRIu64 "", punchOffset, punchLen);
+
+ // Punch hole for this entire stretch.
+ int result = fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, punchOffset,
+ punchLen);
+ if (result < 0) {
+ ALOGE("fallocate failed to punch hole inside apk, error:%d", errno);
+ return false;
+ }
+ }
+ currentSize = end;
+ ++currentSize;
+ }
+
+ IF_ALOGD() {
+ struct stat64 afterPunch;
+ if (int result = lstat64(filePath, &afterPunch); result != 0) {
+ ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
+ return false;
+ }
+ ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
+ ", st_blksize: %ld, st_size: %" PRIu64 "",
+ afterPunch.st_blocks, afterPunch.st_blksize,
+ static_cast<uint64_t>(afterPunch.st_size));
+ }
+ return true;
+}
+
+}; // namespace android
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.h b/core/jni/com_android_internal_content_FileSystemUtils.h
new file mode 100644
index 0000000..52445e2
--- /dev/null
+++ b/core/jni/com_android_internal_content_FileSystemUtils.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <sys/types.h>
+
+namespace android {
+
+/*
+ * This function deallocates space used by zero padding at the end of LOAD segments in given
+ * uncompressed ELF file. Read ELF headers and find out the offset and sizes of LOAD segments.
+ * [fallocate(2)](http://man7.org/linux/man-pages/man2/fallocate.2.html) is used to deallocate the
+ * zero ranges at the end of LOAD segments. If ELF file is present inside of ApK/Zip file, offset to
+ * the start of the ELF file should be provided.
+ */
+bool punchHolesInElf64(const char* filePath, uint64_t offset);
+
+/*
+ * This function punches holes in zero segments of Apk file which are introduced during the
+ * alignment. Alignment tools add padding inside of extra field in local file header. punch holes in
+ * extra field for zero stretches till the actual file content.
+ */
+bool punchHolesInZip(const char* filePath, uint64_t offset, uint16_t extraFieldLen);
+
+} // namespace android
\ No newline at end of file
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 149e57a..9b8dab7 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -28,6 +28,7 @@
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
@@ -36,6 +37,7 @@
#include <memory>
+#include "com_android_internal_content_FileSystemUtils.h"
#include "core_jni_helpers.h"
#define RS_BITCODE_SUFFIX ".bc"
@@ -144,8 +146,9 @@
uint16_t method;
off64_t offset;
-
- if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc)) {
+ uint16_t extraFieldLength;
+ if (!zipFile->getEntryInfo(zipEntry, &method, &uncompLen, nullptr, &offset, &when, &crc,
+ &extraFieldLength)) {
ALOGE("Couldn't read zip entry info\n");
return INSTALL_FAILED_INVALID_APK;
}
@@ -169,6 +172,21 @@
return INSTALL_FAILED_INVALID_APK;
}
+#ifdef ENABLE_PUNCH_HOLES
+ // if library is uncompressed, punch hole in it in place
+ if (!punchHolesInElf64(zipFile->getZipFileName(), offset)) {
+ ALOGW("Failed to punch uncompressed elf file :%s inside apk : %s at offset: "
+ "%" PRIu64 "",
+ fileName, zipFile->getZipFileName(), offset);
+ }
+
+ // if extra field for this zip file is present with some length, possibility is that it is
+ // padding added for zip alignment. Punch holes there too.
+ if (!punchHolesInZip(zipFile->getZipFileName(), offset, extraFieldLength)) {
+ ALOGW("Failed to punch apk : %s at extra field", zipFile->getZipFileName());
+ }
+#endif // ENABLE_PUNCH_HOLES
+
return INSTALL_SUCCEEDED;
}
@@ -269,6 +287,25 @@
return INSTALL_FAILED_CONTAINER_ERROR;
}
+#ifdef ENABLE_PUNCH_HOLES
+ // punch extracted elf files as well. This will fail where compression is on (like f2fs) but it
+ // will be useful for ext4 based systems
+ struct statfs64 fsInfo;
+ int result = statfs64(localFileName, &fsInfo);
+ if (result < 0) {
+ ALOGW("Failed to stat file :%s", localFileName);
+ }
+
+ if (result == 0 && fsInfo.f_type == EXT4_SUPER_MAGIC) {
+ ALOGD("Punching extracted elf file %s on fs:%" PRIu64 "", fileName,
+ static_cast<uint64_t>(fsInfo.f_type));
+ if (!punchHolesInElf64(localFileName, 0)) {
+ ALOGW("Failed to punch extracted elf file :%s from apk : %s", fileName,
+ zipFile->getZipFileName());
+ }
+ }
+#endif // ENABLE_PUNCH_HOLES
+
ALOGV("Successfully moved %s to %s\n", localTmpFileName, localFileName);
return INSTALL_SUCCEEDED;
diff --git a/core/tests/FileSystemUtilsTest/Android.bp b/core/tests/FileSystemUtilsTest/Android.bp
new file mode 100644
index 0000000..53c22df
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/Android.bp
@@ -0,0 +1,78 @@
+// 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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+ default_team: "trendy_team_android_kernel",
+}
+
+cc_library {
+ name: "libpunchtest",
+ stl: "none",
+ host_supported: true,
+ srcs: ["jni/android_test_jni_source.cpp"],
+ header_libs: ["jni_headers"],
+}
+
+android_test_helper_app {
+ name: "embedded_native_libs_test_app",
+ srcs: ["apk_embedded_native_libs/src/**/*.java"],
+ manifest: "apk_embedded_native_libs/embedded_native_libs_test_app.xml",
+ compile_multilib: "64",
+ jni_libs: [
+ "libpunchtest",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ ],
+ use_embedded_native_libs: true,
+}
+
+android_test_helper_app {
+ name: "extract_native_libs_test_app",
+ srcs: ["apk_extract_native_libs/src/**/*.java"],
+ manifest: "apk_extract_native_libs/extract_native_libs_test_app.xml",
+ compile_multilib: "64",
+ jni_libs: [
+ "libpunchtest",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "platform-test-annotations",
+ ],
+ use_embedded_native_libs: false,
+}
+
+java_test_host {
+ name: "FileSystemUtilsTests",
+ // Include all test java files
+ srcs: ["src/**/*.java"],
+ static_libs: [
+ "junit",
+ "platform-test-annotations",
+ "truth",
+ ],
+ libs: [
+ "tradefed",
+ "compatibility-host-util",
+ "compatibility-tradefed",
+ ],
+ data: [
+ ":embedded_native_libs_test_app",
+ ":extract_native_libs_test_app",
+ ],
+ test_suites: ["general-tests"],
+ test_config: "AndroidTest.xml",
+}
diff --git a/core/tests/FileSystemUtilsTest/AndroidManifest.xml b/core/tests/FileSystemUtilsTest/AndroidManifest.xml
new file mode 100644
index 0000000..acd5ef3
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/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"
+ android:installLocation="internalOnly"
+ package="com.android.internal.content.fstests">
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.internal.content.fstests"
+ android:label="Frameworks FileSystemUtils Tests" />
+
+</manifest>
diff --git a/core/tests/FileSystemUtilsTest/AndroidTest.xml b/core/tests/FileSystemUtilsTest/AndroidTest.xml
new file mode 100644
index 0000000..27f49b2
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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="Runs FileSystemUtilsTest.">
+ <option name="test-suite-tag" value="apct"/>
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="embedded_native_libs_test_app.apk" />
+ <option name="test-file-name" value="extract_native_libs_test_app.apk" />
+ </target_preparer>
+
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="FileSystemUtilsTests.jar" />
+ </test>
+</configuration>
diff --git a/core/tests/FileSystemUtilsTest/TEST_MAPPING b/core/tests/FileSystemUtilsTest/TEST_MAPPING
new file mode 100644
index 0000000..89b3a7a
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "FileSystemUtilsTests"
+ }
+ ]
+}
diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml
new file mode 100644
index 0000000..868f7f3
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/embedded_native_libs_test_app.xml
@@ -0,0 +1,35 @@
+<?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.test.embedded">
+ <application android:extractNativeLibs="false">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true"
+ android:process=":NewProcess">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.embedded"/>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java
new file mode 100644
index 0000000..efa2a39
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/MainActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.test.embedded;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+
+public class MainActivity extends Activity {
+
+ static {
+ System.loadLibrary("punchtest");
+ }
+
+ @VisibleForTesting
+ static final String INTENT_TYPE = "android.test.embedded.EMBEDDED_LIB_LOADED";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_1 = "OP1";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_2 = "OP2";
+
+ @VisibleForTesting
+ static final String KEY_RESULT = "RESULT";
+
+ @Override
+ public void onCreate(Bundle savedOnstanceState) {
+ super.onCreate(savedOnstanceState);
+
+ Intent received = getIntent();
+ int op1 = received.getIntExtra(KEY_OPERAND_1, -1);
+ int op2 = received.getIntExtra(KEY_OPERAND_2, -1);
+ int result = add(op1, op2);
+
+ // Send broadcast so that test can know app has launched and lib is loaded
+ // attach result which has been fetched from JNI lib
+ Intent intent = new Intent(INTENT_TYPE);
+ intent.putExtra(KEY_RESULT, result);
+ sendBroadcast(intent);
+ }
+
+ private native int add(int op1, int op2);
+}
diff --git a/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java
new file mode 100644
index 0000000..d7d67b8
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_embedded_native_libs/src/android/test/embedded/PunchEmbeddedLibTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.test.embedded;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class PunchEmbeddedLibTest {
+
+ @Test
+ public void testPunchedNativeLibs_embeddedLib() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ CountDownLatch receivedSignal = new CountDownLatch(1);
+
+ // Test app is expected to receive this and perform addition of operands using punched lib
+ int op1 = 48;
+ int op2 = 75;
+ IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE);
+ BroadcastReceiver broadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ receivedSignal.countDown();
+ int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000);
+ Assert.assertEquals(result, op1 + op2);
+
+ }
+ };
+ context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+ Intent launchIntent =
+ context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1);
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2);
+ context.startActivity(launchIntent);
+
+ Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS));
+ }
+}
diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml
new file mode 100644
index 0000000..6db96f7
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/extract_native_libs_test_app.xml
@@ -0,0 +1,35 @@
+<?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.test.extract">
+ <application android:extractNativeLibs="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MainActivity"
+ android:exported="true"
+ android:process=":NewProcess">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ </intent-filter>
+ </activity>
+ </application>
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.extract"/>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java
new file mode 100644
index 0000000..b1c157e
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/MainActivity.java
@@ -0,0 +1,60 @@
+/*
+ * 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.test.extract;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.VisibleForTesting;
+
+public class MainActivity extends Activity {
+
+ static {
+ System.loadLibrary("punchtest");
+ }
+
+ @VisibleForTesting
+ static final String INTENT_TYPE = "android.test.extract.EXTRACTED_LIB_LOADED";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_1 = "OP1";
+
+ @VisibleForTesting
+ static final String KEY_OPERAND_2 = "OP2";
+
+ @VisibleForTesting
+ static final String KEY_RESULT = "RESULT";
+
+ @Override
+ public void onCreate(Bundle savedOnstanceState) {
+ super.onCreate(savedOnstanceState);
+
+ Intent received = getIntent();
+ int op1 = received.getIntExtra(KEY_OPERAND_1, -1);
+ int op2 = received.getIntExtra(KEY_OPERAND_2, -1);
+ int result = subtract(op1, op2);
+
+ // Send broadcast so that test can know app has launched and lib is loaded
+ // attach result which has been fetched from JNI lib
+ Intent intent = new Intent(INTENT_TYPE);
+ intent.putExtra(KEY_RESULT, result);
+ sendBroadcast(intent);
+ }
+
+ private native int subtract(int op1, int op2);
+}
diff --git a/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java
new file mode 100644
index 0000000..7cc1017
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/apk_extract_native_libs/src/android/test/extract/PunchExtractedLibTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.test.extract;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class PunchExtractedLibTest {
+
+ @Test
+ public void testPunchedNativeLibs_extractedLib() throws Exception {
+ Context context = InstrumentationRegistry.getContext();
+ CountDownLatch receivedSignal = new CountDownLatch(1);
+
+ // Test app is expected to receive this and perform subtraction using extracted lib
+ int op1 = 100;
+ int op2 = 71;
+ IntentFilter intentFilter = new IntentFilter(MainActivity.INTENT_TYPE);
+ BroadcastReceiver broadcastReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ receivedSignal.countDown();
+ int result = intent.getIntExtra(MainActivity.KEY_RESULT, 1000);
+ Assert.assertEquals(result, op1 - op2);
+ }
+ };
+ context.registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_EXPORTED);
+
+ Intent launchIntent =
+ context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_1, op1);
+ launchIntent.putExtra(MainActivity.KEY_OPERAND_2, op2);
+ context.startActivity(launchIntent);
+
+ Assert.assertTrue("Failed to launch app", receivedSignal.await(10, TimeUnit.SECONDS));
+ }
+}
diff --git a/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
new file mode 100644
index 0000000..2a5ba81
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/jni/android_test_jni_source.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#include <jni.h>
+
+// This will be called from embedded_native_libs_test_app
+extern "C" JNIEXPORT
+jint JNICALL Java_android_test_embedded_MainActivity_add(JNIEnv*, jclass, jint op1, jint op2) {
+ return op1 + op2;
+}
+
+// This will be called from extract_native_libs_test_app
+extern "C" JNIEXPORT
+jint JNICALL Java_android_test_extract_MainActivity_subtract(JNIEnv*, jclass, jint op1, jint op2) {
+ return op1 - op2;
+}
+
+// Initialize JNI
+jint JNI_OnLoad(JavaVM *jvm, void */* reserved */) {
+ JNIEnv *e;
+
+ // Check JNI version
+ if (jvm->GetEnv((void **) &e, JNI_VERSION_1_6)) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
new file mode 100644
index 0000000..77802e5
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.android.internal.content;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class FileSystemUtilsTest extends BaseHostJUnit4Test {
+
+ @Test
+ @AppModeFull
+ public void runPunchedApp_embeddedNativeLibs() throws DeviceNotAvailableException {
+ String appPackage = "android.test.embedded";
+ String testName = "PunchEmbeddedLibTest";
+ assertTrue(isPackageInstalled(appPackage));
+ runDeviceTests(appPackage, appPackage + "." + testName);
+ }
+
+ @Test
+ @AppModeFull
+ public void runPunchedApp_extractedNativeLibs() throws DeviceNotAvailableException {
+ String appPackage = "android.test.extract";
+ String testName = "PunchExtractedLibTest";
+ assertTrue(isPackageInstalled(appPackage));
+ runDeviceTests(appPackage, appPackage + "." + testName);
+ }
+}
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 2f2215f..d1d7c14 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -16,8 +16,6 @@
package android.security;
-import android.compat.annotation.UnsupportedAppUsage;
-
/**
* This class provides some constants and helper methods related to Android's Keystore service.
* This class was originally much larger, but its functionality was superseded by other classes.
@@ -30,11 +28,4 @@
// Used for UID field to indicate the calling UID.
public static final int UID_SELF = -1;
-
- private static final KeyStore KEY_STORE = new KeyStore();
-
- @UnsupportedAppUsage
- public static KeyStore getInstance() {
- return KEY_STORE;
- }
}
diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp
index 9d4b426..839c7b6 100644
--- a/libs/androidfw/ZipFileRO.cpp
+++ b/libs/androidfw/ZipFileRO.cpp
@@ -119,30 +119,41 @@
* appear to be bogus.
*/
bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32) const
+{
+ return getEntryInfo(entry, pMethod, pUncompLen, pCompLen, pOffset, pModWhen,
+ pCrc32, nullptr);
+}
+
+bool ZipFileRO::getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
- uint32_t* pModWhen, uint32_t* pCrc32) const
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const
{
const _ZipEntryRO* zipEntry = reinterpret_cast<_ZipEntryRO*>(entry);
const ZipEntry& ze = zipEntry->entry;
- if (pMethod != NULL) {
+ if (pMethod != nullptr) {
*pMethod = ze.method;
}
- if (pUncompLen != NULL) {
+ if (pUncompLen != nullptr) {
*pUncompLen = ze.uncompressed_length;
}
- if (pCompLen != NULL) {
+ if (pCompLen != nullptr) {
*pCompLen = ze.compressed_length;
}
- if (pOffset != NULL) {
+ if (pOffset != nullptr) {
*pOffset = ze.offset;
}
- if (pModWhen != NULL) {
+ if (pModWhen != nullptr) {
*pModWhen = ze.mod_time;
}
- if (pCrc32 != NULL) {
+ if (pCrc32 != nullptr) {
*pCrc32 = ze.crc32;
}
+ if (pExtraFieldSize != nullptr) {
+ *pExtraFieldSize = ze.extra_field_size;
+ }
return true;
}
@@ -310,3 +321,7 @@
return true;
}
+
+const char* ZipFileRO::getZipFileName() {
+ return mFileName;
+}
diff --git a/libs/androidfw/include/androidfw/ZipFileRO.h b/libs/androidfw/include/androidfw/ZipFileRO.h
index be1f98f..f7c5007 100644
--- a/libs/androidfw/include/androidfw/ZipFileRO.h
+++ b/libs/androidfw/include/androidfw/ZipFileRO.h
@@ -151,6 +151,10 @@
uint32_t* pCompLen, off64_t* pOffset, uint32_t* pModWhen,
uint32_t* pCrc32) const;
+ bool getEntryInfo(ZipEntryRO entry, uint16_t* pMethod,
+ uint32_t* pUncompLen, uint32_t* pCompLen, off64_t* pOffset,
+ uint32_t* pModWhen, uint32_t* pCrc32, uint16_t* pExtraFieldSize) const;
+
/*
* Create a new FileMap object that maps a subset of the archive. For
* an uncompressed entry this effectively provides a pointer to the
@@ -187,6 +191,8 @@
*/
bool uncompressEntry(ZipEntryRO entry, int fd) const;
+ const char* getZipFileName();
+
~ZipFileRO();
private:
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 8ed4bf2..3fc8f26 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -361,6 +361,7 @@
* Tests if MR2.SessionCallback.onSessionCreated is called
* when a route is selected from MR2Manager.
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testRouterOnSessionCreated() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -500,6 +501,7 @@
/**
* Tests select, transfer, release of routes of a provider
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testSelectAndTransferAndRelease() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
@@ -878,6 +880,7 @@
* Tests if getSelectableRoutes and getDeselectableRoutes filter routes based on
* selected routes
*/
+ @Ignore // Ignored due to flakiness. No plans to fix though, in favor of removal (b/334970551).
@Test
public void testGetSelectableRoutes_notReturnsSelectedRoutes() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
diff --git a/packages/SettingsLib/aconfig/OWNERS b/packages/SettingsLib/aconfig/OWNERS
new file mode 100644
index 0000000..ba02d20
--- /dev/null
+++ b/packages/SettingsLib/aconfig/OWNERS
@@ -0,0 +1,2 @@
+# go/android-fwk-media-solutions for info on areas of ownership.
+per-file settingslib_media_flag_declarations.aconfig = file:platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/proto/src/am_capabilities.proto b/proto/src/am_capabilities.proto
index d97bf81..fc9f7a45 100644
--- a/proto/src/am_capabilities.proto
+++ b/proto/src/am_capabilities.proto
@@ -7,6 +7,16 @@
string name = 1;
}
+message VMCapability {
+ string name = 1;
+}
+
+message FrameworkCapability {
+ string name = 1;
+}
+
message Capabilities {
repeated Capability values = 1;
+ repeated VMCapability vm_capabilities = 2;
+ repeated FrameworkCapability framework_capabilities = 3;
}
diff --git a/services/Android.bp b/services/Android.bp
index 32a8bbb..888e044 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -285,34 +285,34 @@
baseline_file: "api/lint-baseline.txt",
},
},
- dists: [
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "android-non-updatable.txt",
- },
- {
- targets: ["sdk"],
- dir: "apistubs/android/system-server/api",
- dest: "android-non-updatable-removed.txt",
- },
- ],
soong_config_variables: {
release_hidden_api_exportable_stubs: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable.txt",
tag: ".exportable.api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".exportable.removed-api.txt",
},
],
conditions_default: {
dists: [
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable.txt",
tag: ".api.txt",
},
{
+ targets: ["sdk"],
+ dir: "apistubs/android/system-server/api",
+ dest: "android-non-updatable-removed.txt",
tag: ".removed-api.txt",
},
],
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7c66731..de039fb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -96,6 +96,7 @@
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.Debug;
import android.os.IProgressListener;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
@@ -124,6 +125,8 @@
import com.android.server.am.LowMemDetector.MemFactor;
import com.android.server.am.nano.Capabilities;
import com.android.server.am.nano.Capability;
+import com.android.server.am.nano.FrameworkCapability;
+import com.android.server.am.nano.VMCapability;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.Slogf;
@@ -442,6 +445,22 @@
capabilities.values[i] = cap;
}
+ String[] vmCapabilities = Debug.getVmFeatureList();
+ capabilities.vmCapabilities = new VMCapability[vmCapabilities.length];
+ for (int i = 0; i < vmCapabilities.length; i++) {
+ VMCapability cap = new VMCapability();
+ cap.name = vmCapabilities[i];
+ capabilities.vmCapabilities[i] = cap;
+ }
+
+ String[] fmCapabilities = Debug.getFeatureList();
+ capabilities.frameworkCapabilities = new FrameworkCapability[fmCapabilities.length];
+ for (int i = 0; i < fmCapabilities.length; i++) {
+ FrameworkCapability cap = new FrameworkCapability();
+ cap.name = fmCapabilities[i];
+ capabilities.frameworkCapabilities[i] = cap;
+ }
+
try {
getRawOutputStream().write(Capabilities.toByteArray(capabilities));
} catch (IOException e) {
@@ -451,10 +470,16 @@
} else {
// Unfortunately we don't have protobuf text format capabilities here.
// Fallback to line separated list instead for text parser.
- pw.println("Format: 1");
+ pw.println("Format: 2");
for (String capability : CAPABILITIES) {
pw.println(capability);
}
+ for (String capability : Debug.getVmFeatureList()) {
+ pw.println("vm:" + capability);
+ }
+ for (String capability : Debug.getFeatureList()) {
+ pw.println("framework:" + capability);
+ }
}
return 0;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0bfbee6..5e6cf1a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -7855,6 +7855,7 @@
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADSET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_LINE);
+ DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.add(AudioSystem.DEVICE_OUT_HEARING_AID);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
DEVICE_MEDIA_UNMUTED_ON_PLUG_SET.addAll(AudioSystem.DEVICE_OUT_ALL_USB_SET);
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 10e868d..c1d92cf 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -119,8 +119,13 @@
}
@Override
- public void onStart() {
- publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
+ publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy);
+ }
}
private void notifyTraceur(boolean sessionStolen) {
diff --git a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index ed9fa65..3619253 100644
--- a/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/services/core/java/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -16,8 +16,10 @@
package com.android.server.vcn.routeselection;
+import static com.android.internal.annotations.VisibleForTesting.Visibility;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
@@ -38,6 +40,10 @@
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.server.vcn.VcnContext;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.BitSet;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -56,8 +62,51 @@
public class IpSecPacketLossDetector extends NetworkMetricMonitor {
private static final String TAG = IpSecPacketLossDetector.class.getSimpleName();
+ private static final int PACKET_LOSS_PERCENT_UNAVAILABLE = -1;
+
+ // Ignore the packet loss detection result if the expected packet number is smaller than 10.
+ // Solarwinds NPM uses 10 ICMP echos to calculate packet loss rate (as per
+ // https://thwack.solarwinds.com/products/network-performance-monitor-npm/f/forum/63829/how-is-packet-loss-calculated)
@VisibleForTesting(visibility = Visibility.PRIVATE)
- static final int PACKET_LOSS_UNAVALAIBLE = -1;
+ static final int MIN_VALID_EXPECTED_RX_PACKET_NUM = 10;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"PACKET_LOSS_"},
+ value = {
+ PACKET_LOSS_RATE_VALID,
+ PACKET_LOSS_RATE_INVALID,
+ PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP,
+ })
+ @Target({ElementType.TYPE_USE})
+ private @interface PacketLossResultType {}
+
+ /** Indicates a valid packet loss rate is available */
+ private static final int PACKET_LOSS_RATE_VALID = 0;
+
+ /**
+ * Indicates that the detector cannot get a valid packet loss rate due to one of the following
+ * reasons:
+ *
+ * <ul>
+ * <li>The replay window did not proceed and thus all packets might have been delivered out of
+ * order
+ * <li>The expected received packet number is too small and thus the detection result is not
+ * reliable
+ * <li>There are unexpected errors
+ * </ul>
+ */
+ private static final int PACKET_LOSS_RATE_INVALID = 1;
+
+ /**
+ * The sequence number increase is unusually large and might be caused an intentional leap on
+ * the server's downlink
+ *
+ * <p>Inbound sequence number will not always increase consecutively. During load balancing the
+ * server might add a big leap on the sequence number intentionally. In such case a high packet
+ * loss rate does not always indicate a lossy network
+ */
+ private static final int PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP = 2;
// For VoIP, losses between 5% and 10% of the total packet stream will affect the quality
// significantly (as per "Computer Networking for LANS to WANS: Hardware, Software and
@@ -68,8 +117,12 @@
private static final int POLL_IPSEC_STATE_INTERVAL_SECONDS_DEFAULT = 20;
+ // By default, there's no maximum limit enforced
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
+
private long mPollIpSecStateIntervalMs;
- private final int mPacketLossRatePercentThreshold;
+ private int mPacketLossRatePercentThreshold;
+ private int mMaxSeqNumIncreasePerSecond;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -108,6 +161,7 @@
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
// Register for system broadcasts to monitor idle mode change
final IntentFilter intentFilter = new IntentFilter();
@@ -172,6 +226,24 @@
return IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_DEFAULT;
}
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ static int getMaxSeqNumIncreasePerSecond(@Nullable PersistableBundleWrapper carrierConfig) {
+ int maxSeqNumIncrease = MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ if (Flags.handleSeqNumLeap() && carrierConfig != null) {
+ maxSeqNumIncrease =
+ carrierConfig.getInt(
+ VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY,
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
+ }
+
+ if (maxSeqNumIncrease < MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ logE(TAG, "Invalid value of MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY " + maxSeqNumIncrease);
+ return MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED;
+ }
+
+ return maxSeqNumIncrease;
+ }
+
@Override
protected void onSelectedUnderlyingNetworkChanged() {
if (!isSelectedUnderlyingNetwork()) {
@@ -207,6 +279,11 @@
// The already scheduled event will not be affected. The followup events will be scheduled
// with the new interval
mPollIpSecStateIntervalMs = getPollIpSecStateIntervalMs(carrierConfig);
+
+ if (Flags.handleSeqNumLeap()) {
+ mPacketLossRatePercentThreshold = getPacketLossRatePercentThreshold(carrierConfig);
+ mMaxSeqNumIncreasePerSecond = getMaxSeqNumIncreasePerSecond(carrierConfig);
+ }
}
@Override
@@ -307,30 +384,40 @@
return;
}
- final int packetLossRate =
+ final PacketLossCalculationResult calculateResult =
mPacketLossCalculator.getPacketLossRatePercentage(
- mLastIpSecTransformState, state, getLogPrefix());
+ mLastIpSecTransformState,
+ state,
+ mMaxSeqNumIncreasePerSecond,
+ getLogPrefix());
- if (packetLossRate == PACKET_LOSS_UNAVALAIBLE) {
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_INVALID) {
return;
}
final String logMsg =
- "packetLossRate: "
- + packetLossRate
+ "calculateResult: "
+ + calculateResult
+ "% in the past "
+ (state.getTimestampMillis()
- mLastIpSecTransformState.getTimestampMillis())
+ "ms";
mLastIpSecTransformState = state;
- if (packetLossRate < mPacketLossRatePercentThreshold) {
+ if (calculateResult.getPacketLossRatePercent() < mPacketLossRatePercentThreshold) {
logV(logMsg);
+
+ // In both "valid" or "unusual_seq_num_leap" cases, notify that the network has passed
+ // the validation
onValidationResultReceivedInternal(false /* isFailed */);
} else {
logInfo(logMsg);
- onValidationResultReceivedInternal(true /* isFailed */);
+ if (calculateResult.getResultType() == PACKET_LOSS_RATE_VALID) {
+ onValidationResultReceivedInternal(true /* isFailed */);
+ }
+
+ // In both "valid" or "unusual_seq_num_leap" cases, trigger network validation
if (Flags.validateNetworkOnIpsecLoss()) {
// Trigger re-validation of the underlying network; if it fails, the VCN will
// attempt to migrate away.
@@ -343,9 +430,10 @@
@VisibleForTesting(visibility = Visibility.PRIVATE)
public static class PacketLossCalculator {
/** Calculate the packet loss rate between two timestamps */
- public int getPacketLossRatePercentage(
+ public PacketLossCalculationResult getPacketLossRatePercentage(
@NonNull IpSecTransformState oldState,
@NonNull IpSecTransformState newState,
+ int maxSeqNumIncreasePerSecond,
String logPrefix) {
logVIpSecTransform("oldState", oldState, logPrefix);
logVIpSecTransform("newState", newState, logPrefix);
@@ -359,7 +447,23 @@
if (oldSeqHi == newSeqHi || newSeqHi < replayWindowSize) {
// The replay window did not proceed and all packets might have been delivered out
// of order
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
+ }
+
+ boolean isUnusualSeqNumLeap = false;
+
+ // Handle sequence number leap
+ if (Flags.handleSeqNumLeap()
+ && maxSeqNumIncreasePerSecond != MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED) {
+ final long timeDiffMillis =
+ newState.getTimestampMillis() - oldState.getTimestampMillis();
+ final long maxSeqNumIncrease = timeDiffMillis * maxSeqNumIncreasePerSecond / 1000;
+
+ // Sequence numbers are unsigned 32-bit values. If maxSeqNumIncrease overflows,
+ // isUnusualSeqNumLeap can never be true.
+ if (maxSeqNumIncrease >= 0 && newSeqHi - oldSeqHi >= maxSeqNumIncrease) {
+ isUnusualSeqNumLeap = true;
+ }
}
// Get the expected packet count by assuming there is no packet loss. In this case, SA
@@ -381,15 +485,23 @@
+ " actualPktCntDiff: "
+ actualPktCntDiff);
+ if (Flags.handleSeqNumLeap() && expectedPktCntDiff < MIN_VALID_EXPECTED_RX_PACKET_NUM) {
+ // The sample size is too small to ensure a reliable detection result
+ return PacketLossCalculationResult.invalid();
+ }
+
if (expectedPktCntDiff < 0
|| expectedPktCntDiff == 0
|| actualPktCntDiff < 0
|| actualPktCntDiff > expectedPktCntDiff) {
logWtf(TAG, "Impossible values for expectedPktCntDiff or" + " actualPktCntDiff");
- return PACKET_LOSS_UNAVALAIBLE;
+ return PacketLossCalculationResult.invalid();
}
- return 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ final int percent = 100 - (int) (actualPktCntDiff * 100 / expectedPktCntDiff);
+ return isUnusualSeqNumLeap
+ ? PacketLossCalculationResult.unusualSeqNumLeap(percent)
+ : PacketLossCalculationResult.valid(percent);
}
}
@@ -409,4 +521,64 @@
private static long getPacketCntInReplayWindow(@NonNull IpSecTransformState state) {
return BitSet.valueOf(state.getReplayBitmap()).cardinality();
}
+
+ @VisibleForTesting(visibility = Visibility.PRIVATE)
+ public static class PacketLossCalculationResult {
+ @PacketLossResultType private final int mResultType;
+ private final int mPacketLossRatePercent;
+
+ private PacketLossCalculationResult(@PacketLossResultType int type, int percent) {
+ mResultType = type;
+ mPacketLossRatePercent = percent;
+ }
+
+ /** Construct an instance that contains a valid packet loss rate */
+ public static PacketLossCalculationResult valid(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_RATE_VALID, percent);
+ }
+
+ /** Construct an instance indicating the inability to get a valid packet loss rate */
+ public static PacketLossCalculationResult invalid() {
+ return new PacketLossCalculationResult(
+ PACKET_LOSS_RATE_INVALID, PACKET_LOSS_PERCENT_UNAVAILABLE);
+ }
+
+ /** Construct an instance indicating that there is an unusual sequence number leap */
+ public static PacketLossCalculationResult unusualSeqNumLeap(int percent) {
+ return new PacketLossCalculationResult(PACKET_LOSS_UNUSUAL_SEQ_NUM_LEAP, percent);
+ }
+
+ @PacketLossResultType
+ public int getResultType() {
+ return mResultType;
+ }
+
+ public int getPacketLossRatePercent() {
+ return mPacketLossRatePercent;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResultType, mPacketLossRatePercent);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (!(other instanceof PacketLossCalculationResult)) {
+ return false;
+ }
+
+ final PacketLossCalculationResult rhs = (PacketLossCalculationResult) other;
+ return mResultType == rhs.mResultType
+ && mPacketLossRatePercent == rhs.mPacketLossRatePercent;
+ }
+
+ @Override
+ public String toString() {
+ return "mResultType: "
+ + mResultType
+ + " | mPacketLossRatePercent: "
+ + mPacketLossRatePercent;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index a1b212f..b9b1060 100644
--- a/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/services/core/java/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -272,6 +272,11 @@
}
}
+ protected static void logE(String className, String msgWithPrefix) {
+ Slog.w(className, msgWithPrefix);
+ LOCAL_LOG.log("[ERROR ] " + className + msgWithPrefix);
+ }
+
protected static void logWtf(String className, String msgWithPrefix) {
Slog.wtf(className, msgWithPrefix);
LOCAL_LOG.log("[WTF ] " + className + msgWithPrefix);
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 57939bc..b877787 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -896,8 +896,8 @@
@Override
public void grantInputChannel(int displayId, SurfaceControl surface,
- IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags, int type,
- int inputFeatures, IBinder windowToken, IBinder inputTransferToken,
+ IBinder clientToken, IBinder hostInputToken, int flags, int privateFlags,
+ int inputFeatures, int type, IBinder windowToken, IBinder inputTransferToken,
String inputHandleName, InputChannel outInputChannel) {
if (hostInputToken == null && !mCanAddInternalSystemWindow) {
// Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to
@@ -909,7 +909,7 @@
try {
mService.grantInputChannel(this, mUid, mPid, displayId, surface, clientToken,
hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0,
- type, inputFeatures, windowToken, inputTransferToken, inputHandleName,
+ inputFeatures, type, windowToken, inputTransferToken, inputHandleName,
outInputChannel);
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/core/jni/linux/usb/f_accessory.h b/services/core/jni/linux/usb/f_accessory.h
new file mode 100644
index 0000000..abd864c
--- /dev/null
+++ b/services/core/jni/linux/usb/f_accessory.h
@@ -0,0 +1,34 @@
+/*
+ * This file is auto-generated. Modifications will be lost.
+ *
+ * See https://android.googlesource.com/platform/bionic/+/master/libc/kernel/
+ * for more information.
+ */
+#ifndef _UAPI_LINUX_USB_F_ACCESSORY_H
+#define _UAPI_LINUX_USB_F_ACCESSORY_H
+#define USB_ACCESSORY_VENDOR_ID 0x18D1
+#define USB_ACCESSORY_PRODUCT_ID 0x2D00
+#define USB_ACCESSORY_ADB_PRODUCT_ID 0x2D01
+#define ACCESSORY_STRING_MANUFACTURER 0
+#define ACCESSORY_STRING_MODEL 1
+#define ACCESSORY_STRING_DESCRIPTION 2
+#define ACCESSORY_STRING_VERSION 3
+#define ACCESSORY_STRING_URI 4
+#define ACCESSORY_STRING_SERIAL 5
+#define ACCESSORY_GET_PROTOCOL 51
+#define ACCESSORY_SEND_STRING 52
+#define ACCESSORY_START 53
+#define ACCESSORY_REGISTER_HID 54
+#define ACCESSORY_UNREGISTER_HID 55
+#define ACCESSORY_SET_HID_REPORT_DESC 56
+#define ACCESSORY_SEND_HID_EVENT 57
+#define ACCESSORY_SET_AUDIO_MODE 58
+#define ACCESSORY_GET_STRING_MANUFACTURER _IOW('M', 1, char[256])
+#define ACCESSORY_GET_STRING_MODEL _IOW('M', 2, char[256])
+#define ACCESSORY_GET_STRING_DESCRIPTION _IOW('M', 3, char[256])
+#define ACCESSORY_GET_STRING_VERSION _IOW('M', 4, char[256])
+#define ACCESSORY_GET_STRING_URI _IOW('M', 5, char[256])
+#define ACCESSORY_GET_STRING_SERIAL _IOW('M', 6, char[256])
+#define ACCESSORY_IS_START_REQUESTED _IO('M', 7)
+#define ACCESSORY_GET_AUDIO_MODE _IO('M', 8)
+#endif
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
index f3b164c..f4c4405 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OverlayPackagesProvider.java
@@ -203,6 +203,9 @@
} catch (PackageManager.NameNotFoundException e) {
return false;
}
+ if (packageInfo.applicationInfo == null || packageInfo.applicationInfo.metaData == null) {
+ return false;
+ }
final String metadataKey = sActionToMetadataKeyMap.get(provisioningAction);
return packageInfo.applicationInfo.metaData.getBoolean(metadataKey);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
index 861562d..305108e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING
@@ -1,31 +1,11 @@
{
"presubmit": [
{
- "name": "FrameworksServicesTests",
- "options": [
- {
- "include-filter": "com.android.server.pm."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
- ]
+ "name": "FrameworksServicesTests_pm_presubmit"
}
],
"postsubmit": [
{
- // Presubmit is intentional here while testing with SLO checker.
- // Tests are flaky, waiting to bypass.
- "name": "FrameworksServicesTests_pm_presubmit"
- },
- {
- // Leave postsubmit here when migrating
"name": "FrameworksServicesTests_pm_postsubmit"
}
]
diff --git a/tests/OneMedia/Android.bp b/tests/OneMedia/Android.bp
index 5c73177..a43cd39 100644
--- a/tests/OneMedia/Android.bp
+++ b/tests/OneMedia/Android.bp
@@ -16,6 +16,7 @@
platform_apis: true,
certificate: "platform",
libs: ["org.apache.http.legacy"],
+ optional_uses_libs: ["org.apache.http.legacy"],
optimize: {
enabled: false,
},
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index fdf8fb8..c8b60e5 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -17,9 +17,11 @@
package com.android.server.vcn.routeselection;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY;
+import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY;
import static android.net.vcn.VcnManager.VCN_NETWORK_SELECTION_POLL_IPSEC_STATE_INTERVAL_SECONDS_KEY;
-import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.PACKET_LOSS_UNAVALAIBLE;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.MIN_VALID_EXPECTED_RX_PACKET_NUM;
+import static com.android.server.vcn.routeselection.IpSecPacketLossDetector.getMaxSeqNumIncreasePerSecond;
import static com.android.server.vcn.util.PersistableBundleUtils.PersistableBundleWrapper;
import static org.junit.Assert.assertEquals;
@@ -44,6 +46,7 @@
import android.os.OutcomeReceiver;
import android.os.PowerManager;
+import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculationResult;
import com.android.server.vcn.routeselection.IpSecPacketLossDetector.PacketLossCalculator;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.IpSecTransformWrapper;
import com.android.server.vcn.routeselection.NetworkMetricMonitor.NetworkMetricMonitorCallback;
@@ -65,6 +68,7 @@
private static final int REPLAY_BITMAP_LEN_BYTE = 512;
private static final int REPLAY_BITMAP_LEN_BIT = REPLAY_BITMAP_LEN_BYTE * 8;
private static final int IPSEC_PACKET_LOSS_PERCENT_THRESHOLD = 5;
+ private static final int MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED = -1;
private static final long POLL_IPSEC_STATE_INTERVAL_MS = TimeUnit.SECONDS.toMillis(30L);
@Mock private IpSecTransformWrapper mIpSecTransform;
@@ -91,6 +95,9 @@
eq(VCN_NETWORK_SELECTION_IPSEC_PACKET_LOSS_PERCENT_THRESHOLD_KEY),
anyInt()))
.thenReturn(IPSEC_PACKET_LOSS_PERCENT_THRESHOLD);
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED);
when(mDependencies.getPacketLossCalculator()).thenReturn(mPacketLossCalculator);
@@ -112,6 +119,20 @@
.build();
}
+ private static IpSecTransformState newNextTransformState(
+ IpSecTransformState before,
+ long timeDiffMillis,
+ long rxSeqNoDiff,
+ long packtCountDiff,
+ int packetInWin) {
+ return new IpSecTransformState.Builder()
+ .setTimestampMillis(before.getTimestampMillis() + timeDiffMillis)
+ .setRxHighestSequenceNumber(before.getRxHighestSequenceNumber() + rxSeqNoDiff)
+ .setPacketCount(before.getPacketCount() + packtCountDiff)
+ .setReplayBitmap(newReplayBitmap(packetInWin))
+ .build();
+ }
+
private static byte[] newReplayBitmap(int receivedPktCnt) {
final BitSet bitSet = new BitSet(REPLAY_BITMAP_LEN_BIT);
for (int i = 0; i < receivedPktCnt; i++) {
@@ -165,7 +186,7 @@
// Verify the first polled state is stored
assertEquals(mTransformStateInitial, mIpSecPacketLossDetector.getLastTransformState());
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Verify next poll is scheduled
assertNull(mTestLooper.nextMessage());
@@ -278,7 +299,7 @@
xfrmStateReceiver.onResult(newTransformState(1, 1, newReplayBitmap(1)));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
@Test
@@ -289,17 +310,19 @@
xfrmStateReceiver.onError(new RuntimeException("Test"));
verify(mPacketLossCalculator, never())
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
}
private void checkHandleLossRate(
- int mockPacketLossRate, boolean isLastStateExpectedToUpdate, boolean isCallbackExpected)
+ PacketLossCalculationResult mockPacketLossRate,
+ boolean isLastStateExpectedToUpdate,
+ boolean isCallbackExpected)
throws Exception {
final OutcomeReceiver<IpSecTransformState, RuntimeException> xfrmStateReceiver =
startMonitorAndCaptureStateReceiver();
doReturn(mockPacketLossRate)
.when(mPacketLossCalculator)
- .getPacketLossRatePercentage(any(), any(), anyString());
+ .getPacketLossRatePercentage(any(), any(), anyInt(), anyString());
// Mock receiving two states with mTransformStateInitial and an arbitrary transformNew
final IpSecTransformState transformNew = newTransformState(1, 1, newReplayBitmap(1));
@@ -309,7 +332,10 @@
// Verifications
verify(mPacketLossCalculator)
.getPacketLossRatePercentage(
- eq(mTransformStateInitial), eq(transformNew), anyString());
+ eq(mTransformStateInitial),
+ eq(transformNew),
+ eq(MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED),
+ anyString());
if (isLastStateExpectedToUpdate) {
assertEquals(transformNew, mIpSecPacketLossDetector.getLastTransformState());
@@ -327,30 +353,53 @@
@Test
public void testHandleLossRate_validationPass() throws Exception {
checkHandleLossRate(
- 2, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
}
@Test
public void testHandleLossRate_validationFail() throws Exception {
checkHandleLossRate(
- 22, true /* isLastStateExpectedToUpdate */, true /* isCallbackExpected */);
+ PacketLossCalculationResult.valid(22),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
verify(mConnectivityManager).reportNetworkConnectivity(mNetwork, false);
}
@Test
public void testHandleLossRate_resultUnavalaible() throws Exception {
checkHandleLossRate(
- PACKET_LOSS_UNAVALAIBLE,
+ PacketLossCalculationResult.invalid(),
false /* isLastStateExpectedToUpdate */,
false /* isCallbackExpected */);
}
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_highLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(22),
+ true /* isLastStateExpectedToUpdate */,
+ false /* isCallbackExpected */);
+ }
+
+ @Test
+ public void testHandleLossRate_unusualSeqNumLeap_lowLossRate() throws Exception {
+ checkHandleLossRate(
+ PacketLossCalculationResult.unusualSeqNumLeap(2),
+ true /* isLastStateExpectedToUpdate */,
+ true /* isCallbackExpected */);
+ }
+
private void checkGetPacketLossRate(
- IpSecTransformState oldState, IpSecTransformState newState, int expectedLossRate)
+ IpSecTransformState oldState,
+ IpSecTransformState newState,
+ PacketLossCalculationResult expectedLossRate)
throws Exception {
assertEquals(
expectedLossRate,
- mPacketLossCalculator.getPacketLossRatePercentage(oldState, newState, TAG));
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED, TAG));
}
private void checkGetPacketLossRate(
@@ -362,14 +411,45 @@
throws Exception {
final IpSecTransformState newState =
newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
+ checkGetPacketLossRate(
+ oldState, newState, PacketLossCalculationResult.valid(expectedDataLossRate));
+ }
+
+ private void checkGetPacketLossRate(
+ IpSecTransformState oldState,
+ int rxSeqNo,
+ int packetCount,
+ int packetInWin,
+ PacketLossCalculationResult expectedDataLossRate)
+ throws Exception {
+ final IpSecTransformState newState =
+ newTransformState(rxSeqNo, packetCount, newReplayBitmap(packetInWin));
checkGetPacketLossRate(oldState, newState, expectedDataLossRate);
}
@Test
public void testGetPacketLossRate_replayWindowUnchanged() throws Exception {
checkGetPacketLossRate(
- mTransformStateInitial, mTransformStateInitial, PACKET_LOSS_UNAVALAIBLE);
- checkGetPacketLossRate(mTransformStateInitial, 3000, 2000, 2000, PACKET_LOSS_UNAVALAIBLE);
+ mTransformStateInitial,
+ mTransformStateInitial,
+ PacketLossCalculationResult.invalid());
+ checkGetPacketLossRate(
+ mTransformStateInitial, 3000, 2000, 2000, PacketLossCalculationResult.invalid());
+ }
+
+ @Test
+ public void testGetPacketLossRate_expectedPacketNumTooFew() throws Exception {
+ final int oldRxNo = 4096;
+ final int oldPktCnt = 4096;
+ final int pktCntDiff = MIN_VALID_EXPECTED_RX_PACKET_NUM - 1;
+ final byte[] bitmapReceiveAll = newReplayBitmap(4096);
+
+ final IpSecTransformState oldState =
+ newTransformState(oldRxNo, oldPktCnt, bitmapReceiveAll);
+ final IpSecTransformState newState =
+ newTransformState(oldRxNo + pktCntDiff, oldPktCnt + pktCntDiff, bitmapReceiveAll);
+
+ checkGetPacketLossRate(oldState, newState, PacketLossCalculationResult.invalid());
}
@Test
@@ -419,6 +499,45 @@
checkGetPacketLossRate(oldState, 20000, 14000, 3000, 10);
}
+ private void checkGetPktLossRate_unusualSeqNumLeap(
+ int maxSeqNumIncreasePerSecond,
+ int timeDiffMillis,
+ int rxSeqNoDiff,
+ PacketLossCalculationResult expected)
+ throws Exception {
+ final IpSecTransformState oldState = mTransformStateInitial;
+ final IpSecTransformState newState =
+ newNextTransformState(
+ oldState,
+ timeDiffMillis,
+ rxSeqNoDiff,
+ 1 /* packtCountDiff */,
+ 1 /* packetInWin */);
+
+ assertEquals(
+ expected,
+ mPacketLossCalculator.getPacketLossRatePercentage(
+ oldState, newState, maxSeqNumIncreasePerSecond, TAG));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 30000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.unusualSeqNumLeap(100));
+ }
+
+ @Test
+ public void testGetPktLossRate_unusualSeqNumLeap_smallSeqNumDiff() throws Exception {
+ checkGetPktLossRate_unusualSeqNumLeap(
+ 10000 /* maxSeqNumIncreasePerSecond */,
+ (int) TimeUnit.SECONDS.toMillis(2L),
+ 5000 /* rxSeqNoDiff */,
+ PacketLossCalculationResult.valid(100));
+ }
+
// Verify the polling event is scheduled with expected delays
private void verifyPollEventDelayAndScheduleNext(long expectedDelayMs) {
if (expectedDelayMs > 0) {
@@ -445,4 +564,24 @@
// Verify the 3rd poll is scheduled with configured delay
verifyPollEventDelayAndScheduleNext(POLL_IPSEC_STATE_INTERVAL_MS);
}
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond() throws Exception {
+ final int seqNumLeapNegative = 500_000;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(seqNumLeapNegative, getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
+
+ @Test
+ public void testGetMaxSeqNumIncreasePerSecond_negativeValue() throws Exception {
+ final int seqNumLeapNegative = -10;
+ when(mCarrierConfig.getInt(
+ eq(VCN_NETWORK_SELECTION_MAX_SEQ_NUM_INCREASE_PER_SECOND_KEY), anyInt()))
+ .thenReturn(seqNumLeapNegative);
+ assertEquals(
+ MAX_SEQ_NUM_INCREASE_DEFAULT_DISABLED,
+ getMaxSeqNumIncreasePerSecond(mCarrierConfig));
+ }
}
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
index af6daa1..6189fb0 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/NetworkEvaluationTestBase.java
@@ -123,6 +123,7 @@
mSetFlagsRule.enableFlags(Flags.FLAG_VALIDATE_NETWORK_ON_IPSEC_LOSS);
mSetFlagsRule.enableFlags(Flags.FLAG_EVALUATE_IPSEC_LOSS_ON_LP_NC_CHANGE);
+ mSetFlagsRule.enableFlags(Flags.FLAG_HANDLE_SEQ_NUM_LEAP);
when(mNetwork.getNetId()).thenReturn(-1);