Merge "Do not block broadcast on handler processing" into main
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index b4426a6..d04660d 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -69,7 +69,7 @@
"android.hardware.tetheroffload.control-V1.0-java",
"android.hardware.tetheroffload.control-V1.1-java",
"android.hidl.manager-V1.2-java",
- "net-utils-tethering",
+ "net-utils-connectivity-apks",
"netd-client",
"tetheringstatsprotos",
],
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1958aa8..f6717c5 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -38,6 +38,7 @@
cflags: [
"-Wall",
"-Werror",
+ "-Wextra",
],
sdk_version: "30",
min_sdk_version: "30",
@@ -65,73 +66,46 @@
bpf {
name: "block.o",
srcs: ["block.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
bpf {
name: "dscpPolicy.o",
srcs: ["dscpPolicy.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
+// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "offload.o",
srcs: ["offload.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
+ btf: false,
}
+// This version ships to Android T+ which uses mainline netbpfload.
bpf {
name: "offload@mainline.o",
srcs: ["offload@mainline.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- "-DMAINLINE",
- ],
+ cflags: ["-DMAINLINE"],
}
+// Ships to Android S, the bpfloader of which fails to parse BTF enabled .o's.
bpf {
name: "test.o",
srcs: ["test.c"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
+ btf: false,
}
+// This version ships to Android T+ which uses mainline netbpfload.
bpf {
name: "test@mainline.o",
srcs: ["test@mainline.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- "-DMAINLINE",
- ],
+ cflags: ["-DMAINLINE"],
}
bpf {
name: "clatd.o",
srcs: ["clatd.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
sub_dir: "net_shared",
}
@@ -139,11 +113,6 @@
// WARNING: Android T's non-updatable netd depends on 'netd' string for xt_bpf programs it loads
name: "netd.o",
srcs: ["netd.c"],
- btf: true,
- cflags: [
- "-Wall",
- "-Werror",
- ],
// WARNING: Android T's non-updatable netd depends on 'netd_shared' string for xt_bpf programs
sub_dir: "netd_shared",
}
diff --git a/bpf_progs/bpf_net_helpers.h b/bpf_progs/bpf_net_helpers.h
index 1511ee5..ba2f26b 100644
--- a/bpf_progs/bpf_net_helpers.h
+++ b/bpf_progs/bpf_net_helpers.h
@@ -83,7 +83,7 @@
// try to make the first 'len' header bytes readable/writable via direct packet access
// (note: AFAIK there is no way to ask for only direct packet read without also getting write)
-static inline __always_inline void try_make_writable(struct __sk_buff* skb, int len) {
+static inline __always_inline void try_make_writable(struct __sk_buff* skb, unsigned len) {
if (len > skb->len) len = skb->len;
if (skb->data_end - skb->data < len) bpf_skb_pull_data(skb, len);
}
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index f83e5ae..da6ccbf 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -177,7 +177,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(ip) / sizeof(__u16); ++i) {
sum4 += ((__u16*)&ip)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip.version == 4
@@ -188,7 +188,7 @@
// Calculate the *negative* IPv6 16-bit one's complement checksum of the IPv6 header.
__wsum sum6 = 0;
// We'll end up with a non-zero sum due to ip6->version == 6 (which has '0' bits)
- for (int i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip6) / sizeof(__u16); ++i) {
sum6 += ~((__u16*)ip6)[i]; // note the bitwise negation
}
@@ -321,7 +321,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip4) / sizeof(__u16); ++i) {
sum4 += ((__u16*)ip4)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
@@ -387,7 +387,7 @@
// Calculate the IPv6 16-bit one's complement checksum of the IPv6 header.
__wsum sum6 = 0;
// We'll end up with a non-zero sum due to ip6.version == 6
- for (int i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(ip6) / sizeof(__u16); ++i) {
sum6 += ((__u16*)&ip6)[i];
}
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index b3cde45..f08b007 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -184,7 +184,7 @@
static __always_inline inline void update_##the_stats_map(const struct __sk_buff* const skb, \
const TypeOfKey* const key, \
const struct egress_bool egress, \
- const struct kver_uint kver) { \
+ __unused const struct kver_uint kver) { \
StatsValue* value = bpf_##the_stats_map##_lookup_elem(key); \
if (!value) { \
StatsValue newValue = {}; \
@@ -524,22 +524,12 @@
return match;
}
-// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
-DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace_user", AID_ROOT, AID_SYSTEM,
- bpf_cgroup_ingress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
- "fs_bpf_netd_readonly", "",
- IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
-(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
-}
-
-// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
+// Tracing on Android U+ 5.8+
DEFINE_BPF_PROG_EXT("cgroupskb/ingress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_ingress_trace, KVER_5_8, KVER_INF,
BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
- LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, INGRESS, TRACE_ON, KVER_5_8);
}
@@ -556,22 +546,12 @@
return bpf_traffic_account(skb, INGRESS, TRACE_OFF, KVER_NONE);
}
-// This program is optional, and enables tracing on Android U+, 5.8+ on user builds.
-DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace_user", AID_ROOT, AID_SYSTEM,
- bpf_cgroup_egress_trace_user, KVER_5_8, KVER_INF,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
- "fs_bpf_netd_readonly", "",
- IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
-(struct __sk_buff* skb) {
- return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
-}
-
-// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
+// Tracing on Android U+ 5.8+
DEFINE_BPF_PROG_EXT("cgroupskb/egress/stats$trace", AID_ROOT, AID_SYSTEM,
bpf_cgroup_egress_trace, KVER_5_8, KVER_INF,
BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
- LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
+ LOAD_ON_ENG, LOAD_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
return bpf_traffic_account(skb, EGRESS, TRACE_ON, KVER_5_8);
}
@@ -676,7 +656,7 @@
DEFINE_NETD_BPF_PROG_KVER("cgroupsock/inet_create", AID_ROOT, AID_ROOT, inet_socket_create,
KVER_4_14)
-(struct bpf_sock* sk) {
+(__unused struct bpf_sock* sk) {
// A return value of 1 means allow, everything else means deny.
return (get_app_permissions() & BPF_PERMISSION_INTERNET) ? 1 : 0;
}
@@ -690,7 +670,7 @@
return 1;
}
-static __always_inline inline int check_localhost(struct bpf_sock_addr *ctx) {
+static __always_inline inline int check_localhost(__unused struct bpf_sock_addr *ctx) {
// See include/uapi/linux/bpf.h:
//
// struct bpf_sock_addr {
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 4f152bf..4d908d2 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -127,7 +127,7 @@
static inline __always_inline int do_forward6(struct __sk_buff* skb,
const struct rawip_bool rawip,
const struct stream_bool stream,
- const struct kver_uint kver) {
+ __unused const struct kver_uint kver) {
const bool is_ethernet = !rawip.rawip;
// Must be meta-ethernet IPv6 frame
@@ -343,13 +343,13 @@
// and define no-op stubs for pre-4.14 kernels.
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream6_rawip_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream6_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream6_rawip_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -363,7 +363,7 @@
const int l2_header_size, void* data, const void* data_end,
struct ethhdr* eth, struct iphdr* ip, const struct rawip_bool rawip,
const struct stream_bool stream, const struct updatetime_bool updatetime,
- const bool is_tcp, const struct kver_uint kver) {
+ const bool is_tcp, __unused const struct kver_uint kver) {
const bool is_ethernet = !rawip.rawip;
struct tcphdr* tcph = is_tcp ? (void*)(ip + 1) : NULL;
struct udphdr* udph = is_tcp ? NULL : (void*)(ip + 1);
@@ -593,7 +593,7 @@
// Calculate the IPv4 one's complement checksum of the IPv4 header.
__wsum sum4 = 0;
- for (int i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
+ for (unsigned i = 0; i < sizeof(*ip) / sizeof(__u16); ++i) {
sum4 += ((__u16*)ip)[i];
}
// Note that sum4 is guaranteed to be non-zero by virtue of ip4->version == 4
@@ -780,13 +780,13 @@
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream4_rawip_stub, KVER_NONE, KVER_5_4)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_rawip$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream4_rawip_stub, KVER_NONE, KVER_5_4)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -794,13 +794,13 @@
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_downstream4_ether$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_downstream4_ether_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
DEFINE_BPF_PROG_KVER_RANGE("schedcls/tether_upstream4_ether$stub", TETHERING_UID, TETHERING_GID,
sched_cls_tether_upstream4_ether_stub, KVER_NONE, KVER_4_14)
-(struct __sk_buff* skb) {
+(__unused struct __sk_buff* skb) {
return TC_ACT_PIPE;
}
@@ -808,13 +808,13 @@
DEFINE_BPF_MAP_GRW(tether_dev_map, DEVMAP_HASH, uint32_t, uint32_t, 64, TETHERING_GID)
-static inline __always_inline int do_xdp_forward6(struct xdp_md *ctx, const struct rawip_bool rawip,
- const struct stream_bool stream) {
+static inline __always_inline int do_xdp_forward6(__unused struct xdp_md *ctx,
+ __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) {
return XDP_PASS;
}
-static inline __always_inline int do_xdp_forward4(struct xdp_md *ctx, const struct rawip_bool rawip,
- const struct stream_bool stream) {
+static inline __always_inline int do_xdp_forward4(__unused struct xdp_md *ctx,
+ __unused const struct rawip_bool rawip, __unused const struct stream_bool stream) {
return XDP_PASS;
}
diff --git a/common/FlaggedApi.bp b/common/FlaggedApi.bp
index 21be1d3..d5cfde3 100644
--- a/common/FlaggedApi.bp
+++ b/common/FlaggedApi.bp
@@ -22,6 +22,16 @@
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
+java_aconfig_library {
+ name: "com.android.net.flags-aconfig-java",
+ aconfig_declarations: "com.android.net.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+ min_sdk_version: "30",
+ apex_available: [
+ "com.android.tethering",
+ ],
+}
+
aconfig_declarations {
name: "com.android.net.thread.flags-aconfig",
package: "com.android.net.thread.flags",
diff --git a/common/flags.aconfig b/common/flags.aconfig
index b320b61..1b0da4e 100644
--- a/common/flags.aconfig
+++ b/common/flags.aconfig
@@ -123,3 +123,11 @@
description: "Flag for introducing TETHERING_VIRTUAL type"
bug: "340376953"
}
+
+flag {
+ name: "netstats_add_entries"
+ is_exported: true
+ namespace: "android_core_networking"
+ description: "Flag for NetworkStats#addEntries API"
+ bug: "335680025"
+}
diff --git a/framework-t/Android.bp b/framework-t/Android.bp
index f076f5b..ac78d09 100644
--- a/framework-t/Android.bp
+++ b/framework-t/Android.bp
@@ -137,6 +137,10 @@
// framework-connectivity-pre-jarjar match at runtime.
jarjar_rules: ":framework-connectivity-jarjar-rules",
stub_only_libs: [
+ // static_libs is not used to compile stubs. So libs which have
+ // been included in static_libs might still need to
+ // be in stub_only_libs to be usable when generating the API stubs.
+ "com.android.net.flags-aconfig-java",
// Use prebuilt framework-connectivity stubs to avoid circular dependencies
"sdk_module-lib_current_framework-connectivity",
],
diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt
index 1f1953c..2354882 100644
--- a/framework-t/api/system-current.txt
+++ b/framework-t/api/system-current.txt
@@ -310,6 +310,7 @@
public final class NetworkStats implements java.lang.Iterable<android.net.NetworkStats.Entry> android.os.Parcelable {
ctor public NetworkStats(long, int);
method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+ method @FlaggedApi("com.android.net.flags.netstats_add_entries") @NonNull public android.net.NetworkStats addEntries(@NonNull java.util.List<android.net.NetworkStats.Entry>);
method @NonNull public android.net.NetworkStats addEntry(@NonNull android.net.NetworkStats.Entry);
method public android.net.NetworkStats clone();
method public int describeContents();
@@ -497,16 +498,26 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.PendingOperationalDataset> CREATOR;
}
+ @FlaggedApi("com.android.net.thread.flags.configuration_enabled") public final class ThreadConfiguration implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isDhcpv6PdEnabled();
+ method public boolean isNat64Enabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.thread.ThreadConfiguration> CREATOR;
+ }
+
@FlaggedApi("com.android.net.thread.flags.thread_enabled") public final class ThreadNetworkController {
method public void createRandomizedDataset(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.net.thread.ActiveOperationalDataset,android.net.thread.ThreadNetworkException>);
method public int getThreadVersion();
method public static boolean isAttached(int);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void join(@NonNull android.net.thread.ActiveOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void leave(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void registerConfigurationCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void registerOperationalDatasetCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.thread.ThreadNetworkController.StateCallback);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void scheduleMigration(@NonNull android.net.thread.PendingOperationalDataset, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
method @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED") public void setEnabled(boolean, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.net.thread.ThreadNetworkException>);
+ method @FlaggedApi("com.android.net.thread.flags.configuration_enabled") @RequiresPermission(android.Manifest.permission.THREAD_NETWORK_PRIVILEGED) public void unregisterConfigurationCallback(@NonNull java.util.function.Consumer<android.net.thread.ThreadConfiguration>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_NETWORK_STATE, "android.permission.THREAD_NETWORK_PRIVILEGED"}) public void unregisterOperationalDatasetCallback(@NonNull android.net.thread.ThreadNetworkController.OperationalDatasetCallback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void unregisterStateCallback(@NonNull android.net.thread.ThreadNetworkController.StateCallback);
field public static final int DEVICE_ROLE_CHILD = 2; // 0x2
diff --git a/framework-t/src/android/net/NetworkStats.java b/framework-t/src/android/net/NetworkStats.java
index e9a3f58..a2c4fc3 100644
--- a/framework-t/src/android/net/NetworkStats.java
+++ b/framework-t/src/android/net/NetworkStats.java
@@ -18,6 +18,7 @@
import static com.android.net.module.util.NetworkStatsUtils.multiplySafeByRational;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -33,6 +34,7 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.build.SdkLevel;
+import com.android.net.flags.Flags;
import com.android.net.module.util.CollectionUtils;
import libcore.util.EmptyArray;
@@ -845,6 +847,21 @@
}
/**
+ * Adds multiple entries to a copy of this NetworkStats instance.
+ *
+ * @param entries The entries to add.
+ * @return A new NetworkStats instance with the added entries.
+ */
+ @FlaggedApi(Flags.FLAG_NETSTATS_ADD_ENTRIES)
+ public @NonNull NetworkStats addEntries(@NonNull final List<Entry> entries) {
+ final NetworkStats newStats = this.clone();
+ for (final Entry entry : Objects.requireNonNull(entries)) {
+ newStats.combineValues(entry);
+ }
+ return newStats;
+ }
+
+ /**
* Add given values with an existing row, or create a new row if
* {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
* also be used to subtract values from existing rows.
diff --git a/framework/Android.bp b/framework/Android.bp
index 4eda0aa..deea6b6 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -86,6 +86,7 @@
"framework-wifi.stubs.module_lib",
],
static_libs: [
+ "com.android.net.flags-aconfig-java",
// Not using the latest stable version because all functions in the latest version of
// mdns_aidl_interface are deprecated.
"mdns_aidl_interface-V1-java",
@@ -108,14 +109,13 @@
],
}
+// Library to allow Cronet to use hidden APIs
java_library {
- name: "framework-connectivity-pre-jarjar",
+ name: "framework-connectivity-pre-jarjar-without-cronet",
defaults: [
"framework-connectivity-defaults",
],
static_libs: [
- "httpclient_api",
- "httpclient_impl",
// Framework-connectivity-pre-jarjar is identical to framework-connectivity
// implementation, but without the jarjar rules. However, framework-connectivity
// is not based on framework-connectivity-pre-jarjar, it's rebuilt from source
@@ -133,6 +133,21 @@
"framework-tethering.impl",
"framework-wifi.stubs.module_lib",
],
+ visibility: ["//external/cronet:__subpackages__"],
+}
+
+java_library {
+ name: "framework-connectivity-pre-jarjar",
+ defaults: ["framework-module-defaults"],
+ min_sdk_version: "30",
+ static_libs: [
+ "framework-connectivity-pre-jarjar-without-cronet",
+ "httpclient_api",
+ "httpclient_impl",
+ ],
+ apex_available: [
+ "com.android.tethering",
+ ],
visibility: ["//packages/modules/Connectivity:__subpackages__"],
}
diff --git a/framework/src/android/net/BpfNetMapsUtils.java b/framework/src/android/net/BpfNetMapsUtils.java
index 282a11e..1760fa7 100644
--- a/framework/src/android/net/BpfNetMapsUtils.java
+++ b/framework/src/android/net/BpfNetMapsUtils.java
@@ -261,6 +261,12 @@
IBpfMap<S32, UidOwnerValue> uidOwnerMap,
IBpfMap<S32, U8> dataSaverEnabledMap
) {
+ // System uids are not blocked by firewall chains, see bpf_progs/netd.c
+ // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
+ if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
+ return BLOCKED_REASON_NONE;
+ }
+
final long uidRuleConfig;
final long uidMatch;
try {
@@ -331,12 +337,6 @@
) {
throwIfPreT("isUidBlockedByFirewallChains is not available on pre-T devices");
- // System uids are not blocked by firewall chains, see bpf_progs/netd.c
- // TODO: b/348513058 - use UserHandle.isCore() once it is accessible
- if (UserHandle.getAppId(uid) < Process.FIRST_APPLICATION_UID) {
- return false;
- }
-
final int blockedReasons = getUidNetworkingBlockedReasons(
uid,
configurationMap,
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a6a967b..8cf6e04 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -4163,6 +4163,8 @@
*/
@FilteredCallback(methodId = METHOD_ONAVAILABLE_5ARGS,
calledByCallbackId = CALLBACK_AVAILABLE,
+ // If this list is modified, ConnectivityService#addAvailableStateUpdateCallbacks
+ // needs to be updated too.
mayCall = { METHOD_ONAVAILABLE_4ARGS,
METHOD_ONLOCALNETWORKINFOCHANGED,
METHOD_ONBLOCKEDSTATUSCHANGED_INT })
@@ -4193,6 +4195,8 @@
*/
@FilteredCallback(methodId = METHOD_ONAVAILABLE_4ARGS,
calledByCallbackId = CALLBACK_TRANSITIVE_CALLS_ONLY,
+ // If this list is modified, ConnectivityService#addAvailableStateUpdateCallbacks
+ // needs to be updated too.
mayCall = { METHOD_ONAVAILABLE_1ARG,
METHOD_ONNETWORKSUSPENDED,
METHOD_ONCAPABILITIESCHANGED,
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index 908bb13..b8c0ce7 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -48,10 +48,7 @@
"libbase",
"liblog",
],
- srcs: [
- "loader.cpp",
- "NetBpfLoad.cpp",
- ],
+ srcs: ["NetBpfLoad.cpp"],
apex_available: [
"com.android.tethering",
"//apex_available:platform",
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 0d4a5c4..8b539aa 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017-2023 The Android Open Source Project
+ * Copyright (C) 2018-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.
@@ -14,50 +14,1230 @@
* limitations under the License.
*/
-#ifndef LOG_TAG
#define LOG_TAG "NetBpfLoad"
-#endif
#include <arpa/inet.h>
+#include <cstdlib>
#include <dirent.h>
#include <elf.h>
+#include <errno.h>
#include <error.h>
#include <fcntl.h>
+#include <fstream>
#include <inttypes.h>
+#include <iostream>
#include <linux/bpf.h>
+#include <linux/elf.h>
#include <linux/unistd.h>
+#include <log/log.h>
#include <net/if.h>
+#include <optional>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <unistd.h>
-
+#include <string>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <sysexits.h>
+#include <unistd.h>
+#include <unordered_map>
+#include <vector>
-#include <android/api-level.h>
+#include <android-base/cmsg.h>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <log/log.h>
+#include <android/api-level.h>
#include "BpfSyscallWrappers.h"
#include "bpf/BpfUtils.h"
-#include "loader.h"
+#include "bpf/bpf_map_def.h"
+
+using android::base::EndsWith;
+using android::base::StartsWith;
+using android::base::unique_fd;
+using std::ifstream;
+using std::ios;
+using std::optional;
+using std::string;
+using std::vector;
namespace android {
namespace bpf {
-using base::StartsWith;
-using base::EndsWith;
-using std::string;
-using std::vector;
+// Bpf programs may specify per-program & per-map selinux_context and pin_subdir.
+//
+// The BpfLoader needs to convert these bpf.o specified strings into an enum
+// for internal use (to check that valid values were specified for the specific
+// location of the bpf.o file).
+//
+// It also needs to map selinux_context's into pin_subdir's.
+// This is because of how selinux_context is actually implemented via pin+rename.
+//
+// Thus 'domain' enumerates all selinux_context's/pin_subdir's that the BpfLoader
+// is aware of. Thus there currently needs to be a 1:1 mapping between the two.
+//
+enum class domain : int {
+ unspecified = 0, // means just use the default for that specific pin location
+ tethering, // (S+) fs_bpf_tethering /sys/fs/bpf/tethering
+ net_private, // (T+) fs_bpf_net_private /sys/fs/bpf/net_private
+ net_shared, // (T+) fs_bpf_net_shared /sys/fs/bpf/net_shared
+ netd_readonly, // (T+) fs_bpf_netd_readonly /sys/fs/bpf/netd_readonly
+ netd_shared, // (T+) fs_bpf_netd_shared /sys/fs/bpf/netd_shared
+};
+
+static constexpr domain AllDomains[] = {
+ domain::unspecified,
+ domain::tethering,
+ domain::net_private,
+ domain::net_shared,
+ domain::netd_readonly,
+ domain::netd_shared,
+};
+
+static constexpr bool specified(domain d) {
+ return d != domain::unspecified;
+}
+
+struct Location {
+ const char* const dir = "";
+ const char* const prefix = "";
+};
+
+// Returns the build type string (from ro.build.type).
+const std::string& getBuildType() {
+ static std::string t = android::base::GetProperty("ro.build.type", "unknown");
+ return t;
+}
+
+// The following functions classify the 3 Android build types.
+inline bool isEng() {
+ return getBuildType() == "eng";
+}
+
+inline bool isUser() {
+ return getBuildType() == "user";
+}
+
+inline bool isUserdebug() {
+ return getBuildType() == "userdebug";
+}
+
+#define BPF_FS_PATH "/sys/fs/bpf/"
+
+// Size of the BPF log buffer for verifier logging
+#define BPF_LOAD_LOG_SZ 0xfffff
+
+// Unspecified attach type is 0 which is BPF_CGROUP_INET_INGRESS.
+#define BPF_ATTACH_TYPE_UNSPEC BPF_CGROUP_INET_INGRESS
+
+static unsigned int page_size = static_cast<unsigned int>(getpagesize());
+
+constexpr const char* lookupSelinuxContext(const domain d) {
+ switch (d) {
+ case domain::unspecified: return "";
+ case domain::tethering: return "fs_bpf_tethering";
+ case domain::net_private: return "fs_bpf_net_private";
+ case domain::net_shared: return "fs_bpf_net_shared";
+ case domain::netd_readonly: return "fs_bpf_netd_readonly";
+ case domain::netd_shared: return "fs_bpf_netd_shared";
+ }
+}
+
+domain getDomainFromSelinuxContext(const char s[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupSelinuxContext(d)) >= BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupSelinuxContext(d), BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGE("unrecognized selinux_context '%-32s'", s);
+ // Note: we *can* just abort() here as we only load bpf .o files shipped
+ // in the same mainline module / apex as NetBpfLoad itself.
+ abort();
+}
+
+constexpr const char* lookupPinSubdir(const domain d, const char* const unspecified = "") {
+ switch (d) {
+ case domain::unspecified: return unspecified;
+ case domain::tethering: return "tethering/";
+ case domain::net_private: return "net_private/";
+ case domain::net_shared: return "net_shared/";
+ case domain::netd_readonly: return "netd_readonly/";
+ case domain::netd_shared: return "netd_shared/";
+ }
+};
+
+domain getDomainFromPinSubdir(const char s[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]) {
+ for (domain d : AllDomains) {
+ // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
+ if (strlen(lookupPinSubdir(d)) >= BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE) abort();
+ if (!strncmp(s, lookupPinSubdir(d), BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE)) return d;
+ }
+ ALOGE("unrecognized pin_subdir '%-32s'", s);
+ // Note: we *can* just abort() here as we only load bpf .o files shipped
+ // in the same mainline module / apex as NetBpfLoad itself.
+ abort();
+}
+
+static string pathToObjName(const string& path) {
+ // extract everything after the final slash, ie. this is the filename 'foo@1.o' or 'bar.o'
+ string filename = android::base::Split(path, "/").back();
+ // strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo@1' or 'bar'
+ string name = filename.substr(0, filename.find_last_of('.'));
+ // strip any potential @1 suffix, this will leave us with just 'foo' or 'bar'
+ // this can be used to provide duplicate programs (mux based on the bpfloader version)
+ return name.substr(0, name.find_last_of('@'));
+}
+
+typedef struct {
+ const char* name;
+ enum bpf_prog_type type;
+ enum bpf_attach_type expected_attach_type;
+} sectionType;
+
+/*
+ * Map section name prefixes to program types, the section name will be:
+ * SECTION(<prefix>/<name-of-program>)
+ * For example:
+ * SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
+ * is the name of the program, and tracepoint is the type.
+ *
+ * However, be aware that you should not be directly using the SECTION() macro.
+ * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
+ *
+ * Programs shipped inside the tethering apex should be limited to networking stuff,
+ * as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
+ * since they are less stable abi/api and may conflict with platform uses of bpf.
+ */
+sectionType sectionNameTypes[] = {
+ {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
+ {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
+ {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
+ {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
+ {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
+ {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
+ {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
+ {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
+ {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
+ {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
+ {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
+ {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
+ {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
+ {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
+ {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
+ {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
+ {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
+ {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
+ {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
+ {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
+ {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
+ {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
+ {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
+ {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
+ {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
+};
+
+typedef struct {
+ enum bpf_prog_type type;
+ enum bpf_attach_type expected_attach_type;
+ string name;
+ vector<char> data;
+ vector<char> rel_data;
+ optional<struct bpf_prog_def> prog_def;
+
+ unique_fd prog_fd; /* fd after loading */
+} codeSection;
+
+static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
+ elfFile.seekg(0);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
+
+ return 0;
+}
+
+/* Reads all section header tables into an Shdr array */
+static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
+ Elf64_Ehdr eh;
+ int ret = 0;
+
+ ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ elfFile.seekg(eh.e_shoff);
+ if (elfFile.fail()) return -1;
+
+ /* Read shdr table entries */
+ shTable.resize(eh.e_shnum);
+
+ if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
+
+ return 0;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
+ vector<Elf64_Shdr> shTable;
+ int ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ elfFile.seekg(shTable[id].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ sec.resize(shTable[id].sh_size);
+ if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
+
+ return 0;
+}
+
+/* Read whole section header string table */
+static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
+ Elf64_Ehdr eh;
+ int ret = readElfHeader(elfFile, &eh);
+ if (ret) return ret;
+
+ ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
+ if (ret) return ret;
+
+ return 0;
+}
+
+/* Get name from offset in strtab */
+static int getSymName(ifstream& elfFile, int nameOff, string& name) {
+ int ret;
+ vector<char> secStrTab;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ if (nameOff >= (int)secStrTab.size()) return -1;
+
+ name = string((char*)secStrTab.data() + nameOff);
+ return 0;
+}
+
+/* Reads a full section by name - example to get the GPL license */
+static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
+ vector<char> secStrTab;
+ vector<Elf64_Shdr> shTable;
+ int ret;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ ret = readSectionHeaderStrtab(elfFile, secStrTab);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ char* secname = secStrTab.data() + shTable[i].sh_name;
+ if (!secname) continue;
+
+ if (!strcmp(secname, name)) {
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ }
+ return -2;
+}
+
+unsigned int readSectionUint(const char* name, ifstream& elfFile, unsigned int defVal) {
+ vector<char> theBytes;
+ int ret = readSectionByName(name, elfFile, theBytes);
+ if (ret) {
+ ALOGD("Couldn't find section %s (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else if (theBytes.size() < sizeof(unsigned int)) {
+ ALOGE("Section %s too short (defaulting to %u [0x%x]).", name, defVal, defVal);
+ return defVal;
+ } else {
+ // decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
+ unsigned int value = static_cast<unsigned char>(theBytes[3]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[2]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[1]);
+ value <<= 8;
+ value += static_cast<unsigned char>(theBytes[0]);
+ ALOGI("Section %s value is %u [0x%x]", name, value, value);
+ return value;
+ }
+}
+
+static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
+ int ret;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ if ((int)shTable[i].sh_type != type) continue;
+
+ vector<char> dataTmp;
+ dataTmp.resize(shTable[i].sh_size);
+
+ elfFile.seekg(shTable[i].sh_offset);
+ if (elfFile.fail()) return -1;
+
+ if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
+
+ data = dataTmp;
+ return 0;
+ }
+ return -2;
+}
+
+static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
+ return (a.st_value < b.st_value);
+}
+
+static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
+ int ret, numElems;
+ Elf64_Sym* buf;
+ vector<char> secData;
+
+ ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
+ if (ret) return ret;
+
+ buf = (Elf64_Sym*)secData.data();
+ numElems = (secData.size() / sizeof(Elf64_Sym));
+ data.assign(buf, buf + numElems);
+
+ if (sort) std::sort(data.begin(), data.end(), symCompare);
+ return 0;
+}
+
+static enum bpf_prog_type getSectionType(string& name) {
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) return snt.type;
+
+ return BPF_PROG_TYPE_UNSPEC;
+}
+
+static enum bpf_attach_type getExpectedAttachType(string& name) {
+ for (auto& snt : sectionNameTypes)
+ if (StartsWith(name, snt.name)) return snt.expected_attach_type;
+ return BPF_ATTACH_TYPE_UNSPEC;
+}
+
+/*
+static string getSectionName(enum bpf_prog_type type)
+{
+ for (auto& snt : sectionNameTypes)
+ if (snt.type == type)
+ return string(snt.name);
+
+ return "UNKNOWN SECTION NAME " + std::to_string(type);
+}
+*/
+
+static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
+ size_t sizeOfBpfProgDef) {
+ vector<char> pdData;
+ int ret = readSectionByName("progs", elfFile, pdData);
+ if (ret) return ret;
+
+ if (pdData.size() % sizeOfBpfProgDef) {
+ ALOGE("readProgDefs failed due to improper sized progs section, %zu %% %zu != 0",
+ pdData.size(), sizeOfBpfProgDef);
+ return -1;
+ };
+
+ int progCount = pdData.size() / sizeOfBpfProgDef;
+ pd.resize(progCount);
+ size_t trimmedSize = std::min(sizeOfBpfProgDef, sizeof(struct bpf_prog_def));
+
+ const char* dataPtr = pdData.data();
+ for (auto& p : pd) {
+ // First we zero initialize
+ memset(&p, 0, sizeof(p));
+ // Then we set non-zero defaults
+ p.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&p, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfProgDef;
+ }
+ return 0;
+}
+
+static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
+ optional<unsigned> symbolType = std::nullopt) {
+ int ret;
+ string name;
+ vector<Elf64_Sym> symtab;
+ vector<Elf64_Shdr> shTable;
+
+ ret = readSymTab(elfFile, 1 /* sort */, symtab);
+ if (ret) return ret;
+
+ /* Get index of section */
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+
+ int sec_idx = -1;
+ for (int i = 0; i < (int)shTable.size(); i++) {
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ if (!name.compare(sectionName)) {
+ sec_idx = i;
+ break;
+ }
+ }
+
+ /* No section found with matching name*/
+ if (sec_idx == -1) {
+ ALOGW("No %s section could be found in elf object", sectionName.c_str());
+ return -1;
+ }
+
+ for (int i = 0; i < (int)symtab.size(); i++) {
+ if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
+
+ if (symtab[i].st_shndx == sec_idx) {
+ string s;
+ ret = getSymName(elfFile, symtab[i].st_name, s);
+ if (ret) return ret;
+ names.push_back(s);
+ }
+ }
+
+ return 0;
+}
+
+/* Read a section by its index - for ex to get sec hdr strtab blob */
+static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef) {
+ vector<Elf64_Shdr> shTable;
+ int entries, ret = 0;
+
+ ret = readSectionHeadersAll(elfFile, shTable);
+ if (ret) return ret;
+ entries = shTable.size();
+
+ vector<struct bpf_prog_def> pd;
+ ret = readProgDefs(elfFile, pd, sizeOfBpfProgDef);
+ if (ret) return ret;
+ vector<string> progDefNames;
+ ret = getSectionSymNames(elfFile, "progs", progDefNames);
+ if (!pd.empty() && ret) return ret;
+
+ for (int i = 0; i < entries; i++) {
+ string name;
+ codeSection cs_temp;
+ cs_temp.type = BPF_PROG_TYPE_UNSPEC;
+
+ ret = getSymName(elfFile, shTable[i].sh_name, name);
+ if (ret) return ret;
+
+ enum bpf_prog_type ptype = getSectionType(name);
+
+ if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
+
+ // This must be done before '/' is replaced with '_'.
+ cs_temp.expected_attach_type = getExpectedAttachType(name);
+
+ string oldName = name;
+
+ // convert all slashes to underscores
+ std::replace(name.begin(), name.end(), '/', '_');
+
+ cs_temp.type = ptype;
+ cs_temp.name = name;
+
+ ret = readSectionByIdx(elfFile, i, cs_temp.data);
+ if (ret) return ret;
+ ALOGV("Loaded code section %d (%s)", i, name.c_str());
+
+ vector<string> csSymNames;
+ ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
+ if (ret || !csSymNames.size()) return ret;
+ for (size_t i = 0; i < progDefNames.size(); ++i) {
+ if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
+ cs_temp.prog_def = pd[i];
+ break;
+ }
+ }
+
+ /* Check for rel section */
+ if (cs_temp.data.size() > 0 && i < entries) {
+ ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
+ if (ret) return ret;
+
+ if (name == (".rel" + oldName)) {
+ ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
+ if (ret) return ret;
+ ALOGV("Loaded relo section %d (%s)", i, name.c_str());
+ }
+ }
+
+ if (cs_temp.data.size() > 0) {
+ cs.push_back(std::move(cs_temp));
+ ALOGV("Adding section %d to cs list", i);
+ }
+ }
+ return 0;
+}
+
+static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
+ vector<Elf64_Sym> symtab;
+ int ret = 0;
+
+ ret = readSymTab(elfFile, 0 /* !sort */, symtab);
+ if (ret) return ret;
+
+ if (index >= (int)symtab.size()) return -1;
+
+ return getSymName(elfFile, symtab[index].st_name, name);
+}
+
+static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
+ const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
+ // bpfGetFd... family of functions require at minimum a 4.14 kernel,
+ // so on 4.9-T kernels just pretend the map matches our expectations.
+ // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
+ // This is because the primary failure mode we're trying to detect here
+ // is either a source code misconfiguration (which is likely kernel independent)
+ // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
+ if (!isAtLeastKernelVersion(4, 14, 0)) return true;
+
+ // Assuming fd is a valid Bpf Map file descriptor then
+ // all the following should always succeed on a 4.14+ kernel.
+ // If they somehow do fail, they'll return -1 (and set errno),
+ // which should then cause (among others) a key_size mismatch.
+ int fd_type = bpfGetFdMapType(fd);
+ int fd_key_size = bpfGetFdKeySize(fd);
+ int fd_value_size = bpfGetFdValueSize(fd);
+ int fd_max_entries = bpfGetFdMaxEntries(fd);
+ int fd_map_flags = bpfGetFdMapFlags(fd);
+
+ // DEVMAPs are readonly from the bpf program side's point of view, as such
+ // the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
+ int desired_map_flags = (int)mapDef.map_flags;
+ if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
+ desired_map_flags |= BPF_F_RDONLY_PROG;
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int desired_max_entries = mapDef.max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (desired_max_entries < page_size) desired_max_entries = page_size;
+ }
+
+ // The following checks should *never* trigger, if one of them somehow does,
+ // it probably means a bpf .o file has been changed/replaced at runtime
+ // and bpfloader was manually rerun (normally it should only run *once*
+ // early during the boot process).
+ // Another possibility is that something is misconfigured in the code:
+ // most likely a shared map is declared twice differently.
+ // But such a change should never be checked into the source tree...
+ if ((fd_type == type) &&
+ (fd_key_size == (int)mapDef.key_size) &&
+ (fd_value_size == (int)mapDef.value_size) &&
+ (fd_max_entries == (int)desired_max_entries) &&
+ (fd_map_flags == desired_map_flags)) {
+ return true;
+ }
+
+ ALOGE("bpf map name %s mismatch: desired/found: "
+ "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
+ mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size,
+ fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags);
+ return false;
+}
+
+static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
+ const char* prefix, const size_t sizeOfBpfMapDef,
+ const unsigned int bpfloader_ver) {
+ int ret;
+ vector<char> mdData;
+ vector<struct bpf_map_def> md;
+ vector<string> mapNames;
+ string objName = pathToObjName(string(elfPath));
+
+ ret = readSectionByName("maps", elfFile, mdData);
+ if (ret == -2) return 0; // no maps to read
+ if (ret) return ret;
+
+ if (mdData.size() % sizeOfBpfMapDef) {
+ ALOGE("createMaps failed due to improper sized maps section, %zu %% %zu != 0",
+ mdData.size(), sizeOfBpfMapDef);
+ return -1;
+ };
+
+ int mapCount = mdData.size() / sizeOfBpfMapDef;
+ md.resize(mapCount);
+ size_t trimmedSize = std::min(sizeOfBpfMapDef, sizeof(struct bpf_map_def));
+
+ const char* dataPtr = mdData.data();
+ for (auto& m : md) {
+ // First we zero initialize
+ memset(&m, 0, sizeof(m));
+ // Then we set non-zero defaults
+ m.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
+ m.max_kver = 0xFFFFFFFFu; // matches KVER_INF from bpf_helpers.h
+ // Then we copy over the structure prefix from the ELF file.
+ memcpy(&m, dataPtr, trimmedSize);
+ // Move to next struct in the ELF file
+ dataPtr += sizeOfBpfMapDef;
+ }
+
+ ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return ret;
+
+ unsigned kvers = kernelVersion();
+
+ for (int i = 0; i < (int)mapNames.size(); i++) {
+ if (md[i].zero != 0) abort();
+
+ if (bpfloader_ver < md[i].bpfloader_min_ver) {
+ ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_min_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (bpfloader_ver >= md[i].bpfloader_max_ver) {
+ ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
+ md[i].bpfloader_max_ver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers < md[i].min_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x >= 0x%x",
+ mapNames[i].c_str(), kvers, md[i].min_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if (kvers >= md[i].max_kver) {
+ ALOGI("skipping map %s which requires kernel version 0x%x < 0x%x",
+ mapNames[i].c_str(), kvers, md[i].max_kver);
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) ||
+ (md[i].ignore_on_userdebug && isUserdebug())) {
+ ALOGI("skipping map %s which is ignored on %s builds", mapNames[i].c_str(),
+ getBuildType().c_str());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) ||
+ (isRiscV() && md[i].ignore_on_riscv64)) {
+ ALOGI("skipping map %s which is ignored on %s", mapNames[i].c_str(),
+ describeArch());
+ mapFds.push_back(unique_fd());
+ continue;
+ }
+
+ enum bpf_map_type type = md[i].type;
+ if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
+ // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
+ // of be approximated: ARRAY has the same userspace api, though it is not usable
+ // by the same ebpf programs. However, that's okay because the bpf_redirect_map()
+ // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
+ // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
+ // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
+ // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
+ type = BPF_MAP_TYPE_ARRAY;
+ }
+ if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
+ // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
+ // of be approximated: HASH has the same userspace visible api.
+ // However it cannot be used by ebpf programs in the same way.
+ // Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
+ // would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
+ // One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
+ // programs as being 5.4+...
+ type = BPF_MAP_TYPE_HASH;
+ }
+
+ // The .h file enforces that this is a power of two, and page size will
+ // also always be a power of two, so this logic is actually enough to
+ // force it to be a multiple of the page size, as required by the kernel.
+ unsigned int max_entries = md[i].max_entries;
+ if (type == BPF_MAP_TYPE_RINGBUF) {
+ if (max_entries < page_size) max_entries = page_size;
+ }
+
+ domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
+ if (specified(selinux_context)) {
+ ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
+ md[i].selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
+ }
+
+ domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
+ if (specified(pin_subdir)) {
+ ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
+ static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
+ }
+
+ // Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
+ // except that maps shared across .o's have empty <objName>
+ // Note: <objName> refers to the extension-less basename of the .o file (without @ suffix).
+ string mapPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "map_" +
+ (md[i].shared ? "" : objName) + "_" + mapNames[i];
+ bool reuse = false;
+ unique_fd fd;
+ int saved_errno;
+
+ if (access(mapPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
+ saved_errno = errno;
+ ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
+ reuse = true;
+ } else {
+ union bpf_attr req = {
+ .map_type = type,
+ .key_size = md[i].key_size,
+ .value_size = md[i].value_size,
+ .max_entries = max_entries,
+ .map_flags = md[i].map_flags,
+ };
+ if (isAtLeastKernelVersion(4, 15, 0))
+ strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
+ fd.reset(bpf(BPF_MAP_CREATE, req));
+ saved_errno = errno;
+ ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
+ }
+
+ if (!fd.ok()) return -saved_errno;
+
+ // When reusing a pinned map, we need to check the map type/sizes/etc match, but for
+ // safety (since reuse code path is rare) run these checks even if we just created it.
+ // We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
+ if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_map_" + objName + "_" + mapNames[i];
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, mapPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ ret = chmod(mapPinLoc.c_str(), md[i].mode);
+ if (ret) {
+ int err = errno;
+ ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
+ strerror(err));
+ return -err;
+ }
+ ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
+ if (ret) {
+ int err = errno;
+ ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
+ ret, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int mapId = bpfGetFdMapId(fd);
+ if (mapId == -1) {
+ ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
+ } else {
+ ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
+ }
+
+ mapFds.push_back(std::move(fd));
+ }
+
+ return ret;
+}
+
+/* For debugging, dump all instructions */
+static void dumpIns(char* ins, int size) {
+ for (int row = 0; row < size / 8; row++) {
+ ALOGE("%d: ", row);
+ for (int j = 0; j < 8; j++) {
+ ALOGE("%3x ", ins[(row * 8) + j]);
+ }
+ ALOGE("\n");
+ }
+}
+
+/* For debugging, dump all code sections from cs list */
+static void dumpAllCs(vector<codeSection>& cs) {
+ for (int i = 0; i < (int)cs.size(); i++) {
+ ALOGE("Dumping cs %d, name %s", int(i), cs[i].name.c_str());
+ dumpIns((char*)cs[i].data.data(), cs[i].data.size());
+ ALOGE("-----------");
+ }
+}
+
+static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
+ int insnIndex;
+ struct bpf_insn *insn, *insns;
+
+ insns = (struct bpf_insn*)(insnsPtr);
+
+ insnIndex = offset / sizeof(struct bpf_insn);
+ insn = &insns[insnIndex];
+
+ // Occasionally might be useful for relocation debugging, but pretty spammy
+ if (0) {
+ ALOGV("applying relo to instruction at byte offset: %llu, "
+ "insn offset %d, insn %llx",
+ (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
+ }
+
+ if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
+ ALOGE("Dumping all instructions till ins %d", insnIndex);
+ ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
+ dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
+ return;
+ }
+
+ insn->imm = fd;
+ insn->src_reg = BPF_PSEUDO_MAP_FD;
+}
+
+static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
+ vector<string> mapNames;
+
+ int ret = getSectionSymNames(elfFile, "maps", mapNames);
+ if (ret) return;
+
+ for (int k = 0; k != (int)cs.size(); k++) {
+ Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
+ int n_rel = cs[k].rel_data.size() / sizeof(*rel);
+
+ for (int i = 0; i < n_rel; i++) {
+ int symIndex = ELF64_R_SYM(rel[i].r_info);
+ string symName;
+
+ ret = getSymNameByIdx(elfFile, symIndex, symName);
+ if (ret) return;
+
+ /* Find the map fd and apply relo */
+ for (int j = 0; j < (int)mapNames.size(); j++) {
+ if (!mapNames[j].compare(symName)) {
+ applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
+ break;
+ }
+ }
+ }
+ }
+}
+
+static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
+ const char* prefix, const unsigned int bpfloader_ver) {
+ unsigned kvers = kernelVersion();
+
+ if (!kvers) {
+ ALOGE("unable to get kernel version");
+ return -EINVAL;
+ }
+
+ string objName = pathToObjName(string(elfPath));
+
+ for (int i = 0; i < (int)cs.size(); i++) {
+ unique_fd& fd = cs[i].prog_fd;
+ int ret;
+ string name = cs[i].name;
+
+ if (!cs[i].prog_def.has_value()) {
+ ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
+ return -EINVAL;
+ }
+
+ unsigned min_kver = cs[i].prog_def->min_kver;
+ unsigned max_kver = cs[i].prog_def->max_kver;
+ ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)", i, name.c_str(), min_kver,
+ max_kver, kvers);
+ if (kvers < min_kver) continue;
+ if (kvers >= max_kver) continue;
+
+ unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
+ unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
+ domain selinux_context = getDomainFromSelinuxContext(cs[i].prog_def->selinux_context);
+ domain pin_subdir = getDomainFromPinSubdir(cs[i].prog_def->pin_subdir);
+
+ ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
+ bpfMinVer, bpfMaxVer);
+ if (bpfloader_ver < bpfMinVer) continue;
+ if (bpfloader_ver >= bpfMaxVer) continue;
+
+ if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
+ (cs[i].prog_def->ignore_on_user && isUser()) ||
+ (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) {
+ ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(),
+ getBuildType().c_str());
+ continue;
+ }
+
+ if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) ||
+ (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) ||
+ (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) ||
+ (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) ||
+ (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) {
+ ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch());
+ continue;
+ }
+
+ if (specified(selinux_context)) {
+ ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
+ cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
+ lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
+ }
+
+ if (specified(pin_subdir)) {
+ ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
+ cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
+ lookupPinSubdir(pin_subdir));
+ }
+
+ // strip any potential $foo suffix
+ // this can be used to provide duplicate programs
+ // conditionally loaded based on running kernel version
+ name = name.substr(0, name.find_last_of('$'));
+
+ bool reuse = false;
+ // Format of pin location is
+ // /sys/fs/bpf/<prefix>prog_<objName>_<progName>
+ string progPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "prog_" +
+ objName + '_' + string(name);
+ if (access(progPinLoc.c_str(), F_OK) == 0) {
+ fd.reset(retrieveProgram(progPinLoc.c_str()));
+ ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
+ (!fd.ok() ? std::strerror(errno) : "no error"));
+ reuse = true;
+ } else {
+ vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
+
+ union bpf_attr req = {
+ .prog_type = cs[i].type,
+ .kern_version = kvers,
+ .license = ptr_to_u64(license.c_str()),
+ .insns = ptr_to_u64(cs[i].data.data()),
+ .insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
+ .log_level = 1,
+ .log_buf = ptr_to_u64(log_buf.data()),
+ .log_size = static_cast<__u32>(log_buf.size()),
+ .expected_attach_type = cs[i].expected_attach_type,
+ };
+ if (isAtLeastKernelVersion(4, 15, 0))
+ strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
+ fd.reset(bpf(BPF_PROG_LOAD, req));
+
+ ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
+ cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
+
+ if (!fd.ok()) {
+ vector<string> lines = android::base::Split(log_buf.data(), "\n");
+
+ ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
+ for (const auto& line : lines) ALOGW("%s", line.c_str());
+ ALOGW("BPF_PROG_LOAD - END log_buf contents.");
+
+ if (cs[i].prog_def->optional) {
+ ALOGW("failed program is marked optional - continuing...");
+ continue;
+ }
+ ALOGE("non-optional program failed to load.");
+ }
+ }
+
+ if (!fd.ok()) return fd.get();
+
+ if (!reuse) {
+ if (specified(selinux_context)) {
+ string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
+ "tmp_prog_" + objName + '_' + string(name);
+ ret = bpfFdPin(fd, createLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ ret = renameat2(AT_FDCWD, createLoc.c_str(),
+ AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
+ if (ret) {
+ int err = errno;
+ ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
+ err, strerror(err));
+ return -err;
+ }
+ } else {
+ ret = bpfFdPin(fd, progPinLoc.c_str());
+ if (ret) {
+ int err = errno;
+ ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
+ return -err;
+ }
+ }
+ if (chmod(progPinLoc.c_str(), 0440)) {
+ int err = errno;
+ ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
+ return -err;
+ }
+ if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
+ (gid_t)cs[i].prog_def->gid)) {
+ int err = errno;
+ ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
+ cs[i].prog_def->gid, err, strerror(err));
+ return -err;
+ }
+ }
+
+ int progId = bpfGetFdProgId(fd);
+ if (progId == -1) {
+ ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
+ } else {
+ ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
+ }
+ }
+
+ return 0;
+}
+
+int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
+ const Location& location) {
+ vector<char> license;
+ vector<char> critical;
+ vector<codeSection> cs;
+ vector<unique_fd> mapFds;
+ int ret;
+
+ if (!isCritical) return -1;
+ *isCritical = false;
+
+ ifstream elfFile(elfPath, ios::in | ios::binary);
+ if (!elfFile.is_open()) return -1;
+
+ ret = readSectionByName("critical", elfFile, critical);
+ *isCritical = !ret;
+
+ ret = readSectionByName("license", elfFile, license);
+ if (ret) {
+ ALOGE("Couldn't find license in %s", elfPath);
+ return ret;
+ } else {
+ ALOGD("Loading %s%s ELF object %s with license %s",
+ *isCritical ? "critical for " : "optional", *isCritical ? (char*)critical.data() : "",
+ elfPath, (char*)license.data());
+ }
+
+ // the following default values are for bpfloader V0.0 format which does not include them
+ unsigned int bpfLoaderMinVer =
+ readSectionUint("bpfloader_min_ver", elfFile, DEFAULT_BPFLOADER_MIN_VER);
+ unsigned int bpfLoaderMaxVer =
+ readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
+ unsigned int bpfLoaderMinRequiredVer =
+ readSectionUint("bpfloader_min_required_ver", elfFile, 0);
+ size_t sizeOfBpfMapDef =
+ readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
+ size_t sizeOfBpfProgDef =
+ readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
+
+ // inclusive lower bound check
+ if (bpfloader_ver < bpfLoaderMinVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMinVer);
+ return 0;
+ }
+
+ // exclusive upper bound check
+ if (bpfloader_ver >= bpfLoaderMaxVer) {
+ ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMaxVer);
+ return 0;
+ }
+
+ if (bpfloader_ver < bpfLoaderMinRequiredVer) {
+ ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
+ bpfloader_ver, elfPath, bpfLoaderMinRequiredVer);
+ return -1;
+ }
+
+ ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
+ bpfloader_ver, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
+
+ if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
+ ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
+ DEFAULT_SIZEOF_BPF_MAP_DEF);
+ return -1;
+ }
+
+ if (sizeOfBpfProgDef < DEFAULT_SIZEOF_BPF_PROG_DEF) {
+ ALOGE("sizeof(bpf_prog_def) of %zu is too small (< %d)", sizeOfBpfProgDef,
+ DEFAULT_SIZEOF_BPF_PROG_DEF);
+ return -1;
+ }
+
+ ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef);
+ if (ret) {
+ ALOGE("Couldn't read all code sections in %s", elfPath);
+ return ret;
+ }
+
+ /* Just for future debugging */
+ if (0) dumpAllCs(cs);
+
+ ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef, bpfloader_ver);
+ if (ret) {
+ ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
+ return ret;
+ }
+
+ for (int i = 0; i < (int)mapFds.size(); i++)
+ ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
+
+ applyMapRelo(elfFile, mapFds, cs);
+
+ ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix, bpfloader_ver);
+ if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
+
+ return ret;
+}
static bool exists(const char* const path) {
int v = access(path, F_OK);
diff --git a/netbpfload/loader.cpp b/netbpfload/loader.cpp
deleted file mode 100644
index 5141095..0000000
--- a/netbpfload/loader.cpp
+++ /dev/null
@@ -1,1194 +0,0 @@
-/*
- * Copyright (C) 2018-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.
- */
-
-#define LOG_TAG "NetBpfLoad"
-
-#include <errno.h>
-#include <fcntl.h>
-#include <linux/bpf.h>
-#include <linux/elf.h>
-#include <log/log.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sysexits.h>
-#include <sys/stat.h>
-#include <sys/utsname.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "BpfSyscallWrappers.h"
-#include "bpf/BpfUtils.h"
-#include "bpf/bpf_map_def.h"
-#include "loader.h"
-
-#include <cstdlib>
-#include <fstream>
-#include <iostream>
-#include <optional>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include <android-base/cmsg.h>
-#include <android-base/file.h>
-#include <android-base/properties.h>
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
-
-#define BPF_FS_PATH "/sys/fs/bpf/"
-
-// Size of the BPF log buffer for verifier logging
-#define BPF_LOAD_LOG_SZ 0xfffff
-
-// Unspecified attach type is 0 which is BPF_CGROUP_INET_INGRESS.
-#define BPF_ATTACH_TYPE_UNSPEC BPF_CGROUP_INET_INGRESS
-
-using android::base::StartsWith;
-using android::base::unique_fd;
-using std::ifstream;
-using std::ios;
-using std::optional;
-using std::string;
-using std::vector;
-
-namespace android {
-namespace bpf {
-
-const std::string& getBuildType() {
- static std::string t = android::base::GetProperty("ro.build.type", "unknown");
- return t;
-}
-
-static unsigned int page_size = static_cast<unsigned int>(getpagesize());
-
-constexpr const char* lookupSelinuxContext(const domain d, const char* const unspecified = "") {
- switch (d) {
- case domain::unspecified: return unspecified;
- case domain::tethering: return "fs_bpf_tethering";
- case domain::net_private: return "fs_bpf_net_private";
- case domain::net_shared: return "fs_bpf_net_shared";
- case domain::netd_readonly: return "fs_bpf_netd_readonly";
- case domain::netd_shared: return "fs_bpf_netd_shared";
- default: return "(unrecognized)";
- }
-}
-
-domain getDomainFromSelinuxContext(const char s[BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE]) {
- for (domain d : AllDomains) {
- // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
- if (strlen(lookupSelinuxContext(d)) >= BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE) abort();
- if (!strncmp(s, lookupSelinuxContext(d), BPF_SELINUX_CONTEXT_CHAR_ARRAY_SIZE)) return d;
- }
- ALOGW("ignoring unrecognized selinux_context '%-32s'", s);
- // We should return 'unrecognized' here, however: returning unspecified will
- // result in the system simply using the default context, which in turn
- // will allow future expansion by adding more restrictive selinux types.
- // Older bpfloader will simply ignore that, and use the less restrictive default.
- // This does mean you CANNOT later add a *less* restrictive type than the default.
- //
- // Note: we cannot just abort() here as this might be a mainline module shipped optional update
- return domain::unspecified;
-}
-
-constexpr const char* lookupPinSubdir(const domain d, const char* const unspecified = "") {
- switch (d) {
- case domain::unspecified: return unspecified;
- case domain::tethering: return "tethering/";
- case domain::net_private: return "net_private/";
- case domain::net_shared: return "net_shared/";
- case domain::netd_readonly: return "netd_readonly/";
- case domain::netd_shared: return "netd_shared/";
- default: return "(unrecognized)";
- }
-};
-
-domain getDomainFromPinSubdir(const char s[BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE]) {
- for (domain d : AllDomains) {
- // Not sure how to enforce this at compile time, so abort() bpfloader at boot instead
- if (strlen(lookupPinSubdir(d)) >= BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE) abort();
- if (!strncmp(s, lookupPinSubdir(d), BPF_PIN_SUBDIR_CHAR_ARRAY_SIZE)) return d;
- }
- ALOGE("unrecognized pin_subdir '%-32s'", s);
- // pin_subdir affects the object's full pathname,
- // and thus using the default would change the location and thus our code's ability to find it,
- // hence this seems worth treating as a true error condition.
- //
- // Note: we cannot just abort() here as this might be a mainline module shipped optional update
- // However, our callers will treat this as an error, and stop loading the specific .o,
- // which will fail bpfloader if the .o is marked critical.
- return domain::unrecognized;
-}
-
-static string pathToObjName(const string& path) {
- // extract everything after the final slash, ie. this is the filename 'foo@1.o' or 'bar.o'
- string filename = android::base::Split(path, "/").back();
- // strip off everything from the final period onwards (strip '.o' suffix), ie. 'foo@1' or 'bar'
- string name = filename.substr(0, filename.find_last_of('.'));
- // strip any potential @1 suffix, this will leave us with just 'foo' or 'bar'
- // this can be used to provide duplicate programs (mux based on the bpfloader version)
- return name.substr(0, name.find_last_of('@'));
-}
-
-typedef struct {
- const char* name;
- enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
-} sectionType;
-
-/*
- * Map section name prefixes to program types, the section name will be:
- * SECTION(<prefix>/<name-of-program>)
- * For example:
- * SECTION("tracepoint/sched_switch_func") where sched_switch_funcs
- * is the name of the program, and tracepoint is the type.
- *
- * However, be aware that you should not be directly using the SECTION() macro.
- * Instead use the DEFINE_(BPF|XDP)_(PROG|MAP)... & LICENSE/CRITICAL macros.
- *
- * Programs shipped inside the tethering apex should be limited to networking stuff,
- * as KPROBE, PERF_EVENT, TRACEPOINT are dangerous to use from mainline updatable code,
- * since they are less stable abi/api and may conflict with platform uses of bpf.
- */
-sectionType sectionNameTypes[] = {
- {"bind4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_BIND},
- {"bind6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_BIND},
- {"cgroupskb/", BPF_PROG_TYPE_CGROUP_SKB, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsock/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_ATTACH_TYPE_UNSPEC},
- {"cgroupsockcreate/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_CREATE},
- {"cgroupsockrelease/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET_SOCK_RELEASE},
- {"connect4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET4_CONNECT},
- {"connect6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_INET6_CONNECT},
- {"egress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_EGRESS},
- {"getsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_GETSOCKOPT},
- {"ingress/", BPF_PROG_TYPE_CGROUP_SKB, BPF_CGROUP_INET_INGRESS},
- {"lwt_in/", BPF_PROG_TYPE_LWT_IN, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_out/", BPF_PROG_TYPE_LWT_OUT, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_seg6local/", BPF_PROG_TYPE_LWT_SEG6LOCAL, BPF_ATTACH_TYPE_UNSPEC},
- {"lwt_xmit/", BPF_PROG_TYPE_LWT_XMIT, BPF_ATTACH_TYPE_UNSPEC},
- {"postbind4/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET4_POST_BIND},
- {"postbind6/", BPF_PROG_TYPE_CGROUP_SOCK, BPF_CGROUP_INET6_POST_BIND},
- {"recvmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_RECVMSG},
- {"recvmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_RECVMSG},
- {"schedact/", BPF_PROG_TYPE_SCHED_ACT, BPF_ATTACH_TYPE_UNSPEC},
- {"schedcls/", BPF_PROG_TYPE_SCHED_CLS, BPF_ATTACH_TYPE_UNSPEC},
- {"sendmsg4/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP4_SENDMSG},
- {"sendmsg6/", BPF_PROG_TYPE_CGROUP_SOCK_ADDR, BPF_CGROUP_UDP6_SENDMSG},
- {"setsockopt/", BPF_PROG_TYPE_CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT},
- {"skfilter/", BPF_PROG_TYPE_SOCKET_FILTER, BPF_ATTACH_TYPE_UNSPEC},
- {"sockops/", BPF_PROG_TYPE_SOCK_OPS, BPF_CGROUP_SOCK_OPS},
- {"sysctl", BPF_PROG_TYPE_CGROUP_SYSCTL, BPF_CGROUP_SYSCTL},
- {"xdp/", BPF_PROG_TYPE_XDP, BPF_ATTACH_TYPE_UNSPEC},
-};
-
-typedef struct {
- enum bpf_prog_type type;
- enum bpf_attach_type expected_attach_type;
- string name;
- vector<char> data;
- vector<char> rel_data;
- optional<struct bpf_prog_def> prog_def;
-
- unique_fd prog_fd; /* fd after loading */
-} codeSection;
-
-static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
- elfFile.seekg(0);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
-
- return 0;
-}
-
-/* Reads all section header tables into an Shdr array */
-static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
- Elf64_Ehdr eh;
- int ret = 0;
-
- ret = readElfHeader(elfFile, &eh);
- if (ret) return ret;
-
- elfFile.seekg(eh.e_shoff);
- if (elfFile.fail()) return -1;
-
- /* Read shdr table entries */
- shTable.resize(eh.e_shnum);
-
- if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
-
- return 0;
-}
-
-/* Read a section by its index - for ex to get sec hdr strtab blob */
-static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
- vector<Elf64_Shdr> shTable;
- int ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- elfFile.seekg(shTable[id].sh_offset);
- if (elfFile.fail()) return -1;
-
- sec.resize(shTable[id].sh_size);
- if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
-
- return 0;
-}
-
-/* Read whole section header string table */
-static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
- Elf64_Ehdr eh;
- int ret = readElfHeader(elfFile, &eh);
- if (ret) return ret;
-
- ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
- if (ret) return ret;
-
- return 0;
-}
-
-/* Get name from offset in strtab */
-static int getSymName(ifstream& elfFile, int nameOff, string& name) {
- int ret;
- vector<char> secStrTab;
-
- ret = readSectionHeaderStrtab(elfFile, secStrTab);
- if (ret) return ret;
-
- if (nameOff >= (int)secStrTab.size()) return -1;
-
- name = string((char*)secStrTab.data() + nameOff);
- return 0;
-}
-
-/* Reads a full section by name - example to get the GPL license */
-static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& data) {
- vector<char> secStrTab;
- vector<Elf64_Shdr> shTable;
- int ret;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- ret = readSectionHeaderStrtab(elfFile, secStrTab);
- if (ret) return ret;
-
- for (int i = 0; i < (int)shTable.size(); i++) {
- char* secname = secStrTab.data() + shTable[i].sh_name;
- if (!secname) continue;
-
- if (!strcmp(secname, name)) {
- vector<char> dataTmp;
- dataTmp.resize(shTable[i].sh_size);
-
- elfFile.seekg(shTable[i].sh_offset);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
-
- data = dataTmp;
- return 0;
- }
- }
- return -2;
-}
-
-unsigned int readSectionUint(const char* name, ifstream& elfFile, unsigned int defVal) {
- vector<char> theBytes;
- int ret = readSectionByName(name, elfFile, theBytes);
- if (ret) {
- ALOGD("Couldn't find section %s (defaulting to %u [0x%x]).", name, defVal, defVal);
- return defVal;
- } else if (theBytes.size() < sizeof(unsigned int)) {
- ALOGE("Section %s too short (defaulting to %u [0x%x]).", name, defVal, defVal);
- return defVal;
- } else {
- // decode first 4 bytes as LE32 uint, there will likely be more bytes due to alignment.
- unsigned int value = static_cast<unsigned char>(theBytes[3]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[2]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[1]);
- value <<= 8;
- value += static_cast<unsigned char>(theBytes[0]);
- ALOGI("Section %s value is %u [0x%x]", name, value, value);
- return value;
- }
-}
-
-static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
- int ret;
- vector<Elf64_Shdr> shTable;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- for (int i = 0; i < (int)shTable.size(); i++) {
- if ((int)shTable[i].sh_type != type) continue;
-
- vector<char> dataTmp;
- dataTmp.resize(shTable[i].sh_size);
-
- elfFile.seekg(shTable[i].sh_offset);
- if (elfFile.fail()) return -1;
-
- if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
-
- data = dataTmp;
- return 0;
- }
- return -2;
-}
-
-static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
- return (a.st_value < b.st_value);
-}
-
-static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
- int ret, numElems;
- Elf64_Sym* buf;
- vector<char> secData;
-
- ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
- if (ret) return ret;
-
- buf = (Elf64_Sym*)secData.data();
- numElems = (secData.size() / sizeof(Elf64_Sym));
- data.assign(buf, buf + numElems);
-
- if (sort) std::sort(data.begin(), data.end(), symCompare);
- return 0;
-}
-
-static enum bpf_prog_type getSectionType(string& name) {
- for (auto& snt : sectionNameTypes)
- if (StartsWith(name, snt.name)) return snt.type;
-
- return BPF_PROG_TYPE_UNSPEC;
-}
-
-static enum bpf_attach_type getExpectedAttachType(string& name) {
- for (auto& snt : sectionNameTypes)
- if (StartsWith(name, snt.name)) return snt.expected_attach_type;
- return BPF_ATTACH_TYPE_UNSPEC;
-}
-
-/*
-static string getSectionName(enum bpf_prog_type type)
-{
- for (auto& snt : sectionNameTypes)
- if (snt.type == type)
- return string(snt.name);
-
- return "UNKNOWN SECTION NAME " + std::to_string(type);
-}
-*/
-
-static int readProgDefs(ifstream& elfFile, vector<struct bpf_prog_def>& pd,
- size_t sizeOfBpfProgDef) {
- vector<char> pdData;
- int ret = readSectionByName("progs", elfFile, pdData);
- if (ret) return ret;
-
- if (pdData.size() % sizeOfBpfProgDef) {
- ALOGE("readProgDefs failed due to improper sized progs section, %zu %% %zu != 0",
- pdData.size(), sizeOfBpfProgDef);
- return -1;
- };
-
- int progCount = pdData.size() / sizeOfBpfProgDef;
- pd.resize(progCount);
- size_t trimmedSize = std::min(sizeOfBpfProgDef, sizeof(struct bpf_prog_def));
-
- const char* dataPtr = pdData.data();
- for (auto& p : pd) {
- // First we zero initialize
- memset(&p, 0, sizeof(p));
- // Then we set non-zero defaults
- p.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
- // Then we copy over the structure prefix from the ELF file.
- memcpy(&p, dataPtr, trimmedSize);
- // Move to next struct in the ELF file
- dataPtr += sizeOfBpfProgDef;
- }
- return 0;
-}
-
-static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
- optional<unsigned> symbolType = std::nullopt) {
- int ret;
- string name;
- vector<Elf64_Sym> symtab;
- vector<Elf64_Shdr> shTable;
-
- ret = readSymTab(elfFile, 1 /* sort */, symtab);
- if (ret) return ret;
-
- /* Get index of section */
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
-
- int sec_idx = -1;
- for (int i = 0; i < (int)shTable.size(); i++) {
- ret = getSymName(elfFile, shTable[i].sh_name, name);
- if (ret) return ret;
-
- if (!name.compare(sectionName)) {
- sec_idx = i;
- break;
- }
- }
-
- /* No section found with matching name*/
- if (sec_idx == -1) {
- ALOGW("No %s section could be found in elf object", sectionName.c_str());
- return -1;
- }
-
- for (int i = 0; i < (int)symtab.size(); i++) {
- if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
-
- if (symtab[i].st_shndx == sec_idx) {
- string s;
- ret = getSymName(elfFile, symtab[i].st_name, s);
- if (ret) return ret;
- names.push_back(s);
- }
- }
-
- return 0;
-}
-
-/* Read a section by its index - for ex to get sec hdr strtab blob */
-static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs, size_t sizeOfBpfProgDef) {
- vector<Elf64_Shdr> shTable;
- int entries, ret = 0;
-
- ret = readSectionHeadersAll(elfFile, shTable);
- if (ret) return ret;
- entries = shTable.size();
-
- vector<struct bpf_prog_def> pd;
- ret = readProgDefs(elfFile, pd, sizeOfBpfProgDef);
- if (ret) return ret;
- vector<string> progDefNames;
- ret = getSectionSymNames(elfFile, "progs", progDefNames);
- if (!pd.empty() && ret) return ret;
-
- for (int i = 0; i < entries; i++) {
- string name;
- codeSection cs_temp;
- cs_temp.type = BPF_PROG_TYPE_UNSPEC;
-
- ret = getSymName(elfFile, shTable[i].sh_name, name);
- if (ret) return ret;
-
- enum bpf_prog_type ptype = getSectionType(name);
-
- if (ptype == BPF_PROG_TYPE_UNSPEC) continue;
-
- // This must be done before '/' is replaced with '_'.
- cs_temp.expected_attach_type = getExpectedAttachType(name);
-
- string oldName = name;
-
- // convert all slashes to underscores
- std::replace(name.begin(), name.end(), '/', '_');
-
- cs_temp.type = ptype;
- cs_temp.name = name;
-
- ret = readSectionByIdx(elfFile, i, cs_temp.data);
- if (ret) return ret;
- ALOGV("Loaded code section %d (%s)", i, name.c_str());
-
- vector<string> csSymNames;
- ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
- if (ret || !csSymNames.size()) return ret;
- for (size_t i = 0; i < progDefNames.size(); ++i) {
- if (!progDefNames[i].compare(csSymNames[0] + "_def")) {
- cs_temp.prog_def = pd[i];
- break;
- }
- }
-
- /* Check for rel section */
- if (cs_temp.data.size() > 0 && i < entries) {
- ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
- if (ret) return ret;
-
- if (name == (".rel" + oldName)) {
- ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
- if (ret) return ret;
- ALOGV("Loaded relo section %d (%s)", i, name.c_str());
- }
- }
-
- if (cs_temp.data.size() > 0) {
- cs.push_back(std::move(cs_temp));
- ALOGV("Adding section %d to cs list", i);
- }
- }
- return 0;
-}
-
-static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
- vector<Elf64_Sym> symtab;
- int ret = 0;
-
- ret = readSymTab(elfFile, 0 /* !sort */, symtab);
- if (ret) return ret;
-
- if (index >= (int)symtab.size()) return -1;
-
- return getSymName(elfFile, symtab[index].st_name, name);
-}
-
-static bool mapMatchesExpectations(const unique_fd& fd, const string& mapName,
- const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
- // bpfGetFd... family of functions require at minimum a 4.14 kernel,
- // so on 4.9-T kernels just pretend the map matches our expectations.
- // Additionally we'll get almost equivalent test coverage on newer devices/kernels.
- // This is because the primary failure mode we're trying to detect here
- // is either a source code misconfiguration (which is likely kernel independent)
- // or a newly introduced kernel feature/bug (which is unlikely to get backported to 4.9).
- if (!isAtLeastKernelVersion(4, 14, 0)) return true;
-
- // Assuming fd is a valid Bpf Map file descriptor then
- // all the following should always succeed on a 4.14+ kernel.
- // If they somehow do fail, they'll return -1 (and set errno),
- // which should then cause (among others) a key_size mismatch.
- int fd_type = bpfGetFdMapType(fd);
- int fd_key_size = bpfGetFdKeySize(fd);
- int fd_value_size = bpfGetFdValueSize(fd);
- int fd_max_entries = bpfGetFdMaxEntries(fd);
- int fd_map_flags = bpfGetFdMapFlags(fd);
-
- // DEVMAPs are readonly from the bpf program side's point of view, as such
- // the kernel in kernel/bpf/devmap.c dev_map_init_map() will set the flag
- int desired_map_flags = (int)mapDef.map_flags;
- if (type == BPF_MAP_TYPE_DEVMAP || type == BPF_MAP_TYPE_DEVMAP_HASH)
- desired_map_flags |= BPF_F_RDONLY_PROG;
-
- // The .h file enforces that this is a power of two, and page size will
- // also always be a power of two, so this logic is actually enough to
- // force it to be a multiple of the page size, as required by the kernel.
- unsigned int desired_max_entries = mapDef.max_entries;
- if (type == BPF_MAP_TYPE_RINGBUF) {
- if (desired_max_entries < page_size) desired_max_entries = page_size;
- }
-
- // The following checks should *never* trigger, if one of them somehow does,
- // it probably means a bpf .o file has been changed/replaced at runtime
- // and bpfloader was manually rerun (normally it should only run *once*
- // early during the boot process).
- // Another possibility is that something is misconfigured in the code:
- // most likely a shared map is declared twice differently.
- // But such a change should never be checked into the source tree...
- if ((fd_type == type) &&
- (fd_key_size == (int)mapDef.key_size) &&
- (fd_value_size == (int)mapDef.value_size) &&
- (fd_max_entries == (int)desired_max_entries) &&
- (fd_map_flags == desired_map_flags)) {
- return true;
- }
-
- ALOGE("bpf map name %s mismatch: desired/found: "
- "type:%d/%d key:%u/%d value:%u/%d entries:%u/%d flags:%u/%d",
- mapName.c_str(), type, fd_type, mapDef.key_size, fd_key_size, mapDef.value_size,
- fd_value_size, mapDef.max_entries, fd_max_entries, desired_map_flags, fd_map_flags);
- return false;
-}
-
-static int createMaps(const char* elfPath, ifstream& elfFile, vector<unique_fd>& mapFds,
- const char* prefix, const size_t sizeOfBpfMapDef,
- const unsigned int bpfloader_ver) {
- int ret;
- vector<char> mdData;
- vector<struct bpf_map_def> md;
- vector<string> mapNames;
- string objName = pathToObjName(string(elfPath));
-
- ret = readSectionByName("maps", elfFile, mdData);
- if (ret == -2) return 0; // no maps to read
- if (ret) return ret;
-
- if (mdData.size() % sizeOfBpfMapDef) {
- ALOGE("createMaps failed due to improper sized maps section, %zu %% %zu != 0",
- mdData.size(), sizeOfBpfMapDef);
- return -1;
- };
-
- int mapCount = mdData.size() / sizeOfBpfMapDef;
- md.resize(mapCount);
- size_t trimmedSize = std::min(sizeOfBpfMapDef, sizeof(struct bpf_map_def));
-
- const char* dataPtr = mdData.data();
- for (auto& m : md) {
- // First we zero initialize
- memset(&m, 0, sizeof(m));
- // Then we set non-zero defaults
- m.bpfloader_max_ver = DEFAULT_BPFLOADER_MAX_VER; // v1.0
- m.max_kver = 0xFFFFFFFFu; // matches KVER_INF from bpf_helpers.h
- // Then we copy over the structure prefix from the ELF file.
- memcpy(&m, dataPtr, trimmedSize);
- // Move to next struct in the ELF file
- dataPtr += sizeOfBpfMapDef;
- }
-
- ret = getSectionSymNames(elfFile, "maps", mapNames);
- if (ret) return ret;
-
- unsigned kvers = kernelVersion();
-
- for (int i = 0; i < (int)mapNames.size(); i++) {
- if (md[i].zero != 0) abort();
-
- if (bpfloader_ver < md[i].bpfloader_min_ver) {
- ALOGI("skipping map %s which requires bpfloader min ver 0x%05x", mapNames[i].c_str(),
- md[i].bpfloader_min_ver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (bpfloader_ver >= md[i].bpfloader_max_ver) {
- ALOGI("skipping map %s which requires bpfloader max ver 0x%05x", mapNames[i].c_str(),
- md[i].bpfloader_max_ver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (kvers < md[i].min_kver) {
- ALOGI("skipping map %s which requires kernel version 0x%x >= 0x%x",
- mapNames[i].c_str(), kvers, md[i].min_kver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if (kvers >= md[i].max_kver) {
- ALOGI("skipping map %s which requires kernel version 0x%x < 0x%x",
- mapNames[i].c_str(), kvers, md[i].max_kver);
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if ((md[i].ignore_on_eng && isEng()) || (md[i].ignore_on_user && isUser()) ||
- (md[i].ignore_on_userdebug && isUserdebug())) {
- ALOGI("skipping map %s which is ignored on %s builds", mapNames[i].c_str(),
- getBuildType().c_str());
- mapFds.push_back(unique_fd());
- continue;
- }
-
- if ((isArm() && isKernel32Bit() && md[i].ignore_on_arm32) ||
- (isArm() && isKernel64Bit() && md[i].ignore_on_aarch64) ||
- (isX86() && isKernel32Bit() && md[i].ignore_on_x86_32) ||
- (isX86() && isKernel64Bit() && md[i].ignore_on_x86_64) ||
- (isRiscV() && md[i].ignore_on_riscv64)) {
- ALOGI("skipping map %s which is ignored on %s", mapNames[i].c_str(),
- describeArch());
- mapFds.push_back(unique_fd());
- continue;
- }
-
- enum bpf_map_type type = md[i].type;
- if (type == BPF_MAP_TYPE_DEVMAP && !isAtLeastKernelVersion(4, 14, 0)) {
- // On Linux Kernels older than 4.14 this map type doesn't exist, but it can kind
- // of be approximated: ARRAY has the same userspace api, though it is not usable
- // by the same ebpf programs. However, that's okay because the bpf_redirect_map()
- // helper doesn't exist on 4.9-T anyway (so the bpf program would fail to load,
- // and thus needs to be tagged as 4.14+ either way), so there's nothing useful you
- // could do with a DEVMAP anyway (that isn't already provided by an ARRAY)...
- // Hence using an ARRAY instead of a DEVMAP simply makes life easier for userspace.
- type = BPF_MAP_TYPE_ARRAY;
- }
- if (type == BPF_MAP_TYPE_DEVMAP_HASH && !isAtLeastKernelVersion(5, 4, 0)) {
- // On Linux Kernels older than 5.4 this map type doesn't exist, but it can kind
- // of be approximated: HASH has the same userspace visible api.
- // However it cannot be used by ebpf programs in the same way.
- // Since bpf_redirect_map() only requires 4.14, a program using a DEVMAP_HASH map
- // would fail to load (due to trying to redirect to a HASH instead of DEVMAP_HASH).
- // One must thus tag any BPF_MAP_TYPE_DEVMAP_HASH + bpf_redirect_map() using
- // programs as being 5.4+...
- type = BPF_MAP_TYPE_HASH;
- }
-
- // The .h file enforces that this is a power of two, and page size will
- // also always be a power of two, so this logic is actually enough to
- // force it to be a multiple of the page size, as required by the kernel.
- unsigned int max_entries = md[i].max_entries;
- if (type == BPF_MAP_TYPE_RINGBUF) {
- if (max_entries < page_size) max_entries = page_size;
- }
-
- domain selinux_context = getDomainFromSelinuxContext(md[i].selinux_context);
- if (specified(selinux_context)) {
- ALOGI("map %s selinux_context [%-32s] -> %d -> '%s' (%s)", mapNames[i].c_str(),
- md[i].selinux_context, static_cast<int>(selinux_context),
- lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
- }
-
- domain pin_subdir = getDomainFromPinSubdir(md[i].pin_subdir);
- if (unrecognized(pin_subdir)) return -ENOTDIR;
- if (specified(pin_subdir)) {
- ALOGI("map %s pin_subdir [%-32s] -> %d -> '%s'", mapNames[i].c_str(), md[i].pin_subdir,
- static_cast<int>(pin_subdir), lookupPinSubdir(pin_subdir));
- }
-
- // Format of pin location is /sys/fs/bpf/<pin_subdir|prefix>map_<objName>_<mapName>
- // except that maps shared across .o's have empty <objName>
- // Note: <objName> refers to the extension-less basename of the .o file (without @ suffix).
- string mapPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "map_" +
- (md[i].shared ? "" : objName) + "_" + mapNames[i];
- bool reuse = false;
- unique_fd fd;
- int saved_errno;
-
- if (access(mapPinLoc.c_str(), F_OK) == 0) {
- fd.reset(mapRetrieveRO(mapPinLoc.c_str()));
- saved_errno = errno;
- ALOGD("bpf_create_map reusing map %s, ret: %d", mapNames[i].c_str(), fd.get());
- reuse = true;
- } else {
- union bpf_attr req = {
- .map_type = type,
- .key_size = md[i].key_size,
- .value_size = md[i].value_size,
- .max_entries = max_entries,
- .map_flags = md[i].map_flags,
- };
- if (isAtLeastKernelVersion(4, 15, 0))
- strlcpy(req.map_name, mapNames[i].c_str(), sizeof(req.map_name));
- fd.reset(bpf(BPF_MAP_CREATE, req));
- saved_errno = errno;
- ALOGD("bpf_create_map name %s, ret: %d", mapNames[i].c_str(), fd.get());
- }
-
- if (!fd.ok()) return -saved_errno;
-
- // When reusing a pinned map, we need to check the map type/sizes/etc match, but for
- // safety (since reuse code path is rare) run these checks even if we just created it.
- // We assume failure is due to pinned map mismatch, hence the 'NOT UNIQUE' return code.
- if (!mapMatchesExpectations(fd, mapNames[i], md[i], type)) return -ENOTUNIQ;
-
- if (!reuse) {
- if (specified(selinux_context)) {
- string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
- "tmp_map_" + objName + "_" + mapNames[i];
- ret = bpfFdPin(fd, createLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- ret = renameat2(AT_FDCWD, createLoc.c_str(),
- AT_FDCWD, mapPinLoc.c_str(), RENAME_NOREPLACE);
- if (ret) {
- int err = errno;
- ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), mapPinLoc.c_str(), ret,
- err, strerror(err));
- return -err;
- }
- } else {
- ret = bpfFdPin(fd, mapPinLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("pin %s -> %d [%d:%s]", mapPinLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- }
- ret = chmod(mapPinLoc.c_str(), md[i].mode);
- if (ret) {
- int err = errno;
- ALOGE("chmod(%s, 0%o) = %d [%d:%s]", mapPinLoc.c_str(), md[i].mode, ret, err,
- strerror(err));
- return -err;
- }
- ret = chown(mapPinLoc.c_str(), (uid_t)md[i].uid, (gid_t)md[i].gid);
- if (ret) {
- int err = errno;
- ALOGE("chown(%s, %u, %u) = %d [%d:%s]", mapPinLoc.c_str(), md[i].uid, md[i].gid,
- ret, err, strerror(err));
- return -err;
- }
- }
-
- int mapId = bpfGetFdMapId(fd);
- if (mapId == -1) {
- ALOGE("bpfGetFdMapId failed, ret: %d [%d]", mapId, errno);
- } else {
- ALOGI("map %s id %d", mapPinLoc.c_str(), mapId);
- }
-
- mapFds.push_back(std::move(fd));
- }
-
- return ret;
-}
-
-/* For debugging, dump all instructions */
-static void dumpIns(char* ins, int size) {
- for (int row = 0; row < size / 8; row++) {
- ALOGE("%d: ", row);
- for (int j = 0; j < 8; j++) {
- ALOGE("%3x ", ins[(row * 8) + j]);
- }
- ALOGE("\n");
- }
-}
-
-/* For debugging, dump all code sections from cs list */
-static void dumpAllCs(vector<codeSection>& cs) {
- for (int i = 0; i < (int)cs.size(); i++) {
- ALOGE("Dumping cs %d, name %s", int(i), cs[i].name.c_str());
- dumpIns((char*)cs[i].data.data(), cs[i].data.size());
- ALOGE("-----------");
- }
-}
-
-static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
- int insnIndex;
- struct bpf_insn *insn, *insns;
-
- insns = (struct bpf_insn*)(insnsPtr);
-
- insnIndex = offset / sizeof(struct bpf_insn);
- insn = &insns[insnIndex];
-
- // Occasionally might be useful for relocation debugging, but pretty spammy
- if (0) {
- ALOGV("applying relo to instruction at byte offset: %llu, "
- "insn offset %d, insn %llx",
- (unsigned long long)offset, insnIndex, *(unsigned long long*)insn);
- }
-
- if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
- ALOGE("Dumping all instructions till ins %d", insnIndex);
- ALOGE("invalid relo for insn %d: code 0x%x", insnIndex, insn->code);
- dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
- return;
- }
-
- insn->imm = fd;
- insn->src_reg = BPF_PSEUDO_MAP_FD;
-}
-
-static void applyMapRelo(ifstream& elfFile, vector<unique_fd> &mapFds, vector<codeSection>& cs) {
- vector<string> mapNames;
-
- int ret = getSectionSymNames(elfFile, "maps", mapNames);
- if (ret) return;
-
- for (int k = 0; k != (int)cs.size(); k++) {
- Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
- int n_rel = cs[k].rel_data.size() / sizeof(*rel);
-
- for (int i = 0; i < n_rel; i++) {
- int symIndex = ELF64_R_SYM(rel[i].r_info);
- string symName;
-
- ret = getSymNameByIdx(elfFile, symIndex, symName);
- if (ret) return;
-
- /* Find the map fd and apply relo */
- for (int j = 0; j < (int)mapNames.size(); j++) {
- if (!mapNames[j].compare(symName)) {
- applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
- break;
- }
- }
- }
- }
-}
-
-static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
- const char* prefix, const unsigned int bpfloader_ver) {
- unsigned kvers = kernelVersion();
-
- if (!kvers) {
- ALOGE("unable to get kernel version");
- return -EINVAL;
- }
-
- string objName = pathToObjName(string(elfPath));
-
- for (int i = 0; i < (int)cs.size(); i++) {
- unique_fd& fd = cs[i].prog_fd;
- int ret;
- string name = cs[i].name;
-
- if (!cs[i].prog_def.has_value()) {
- ALOGE("[%d] '%s' missing program definition! bad bpf.o build?", i, name.c_str());
- return -EINVAL;
- }
-
- unsigned min_kver = cs[i].prog_def->min_kver;
- unsigned max_kver = cs[i].prog_def->max_kver;
- ALOGD("cs[%d].name:%s min_kver:%x .max_kver:%x (kvers:%x)", i, name.c_str(), min_kver,
- max_kver, kvers);
- if (kvers < min_kver) continue;
- if (kvers >= max_kver) continue;
-
- unsigned bpfMinVer = cs[i].prog_def->bpfloader_min_ver;
- unsigned bpfMaxVer = cs[i].prog_def->bpfloader_max_ver;
- domain selinux_context = getDomainFromSelinuxContext(cs[i].prog_def->selinux_context);
- domain pin_subdir = getDomainFromPinSubdir(cs[i].prog_def->pin_subdir);
- // Note: make sure to only check for unrecognized *after* verifying bpfloader
- // version limits include this bpfloader's version.
-
- ALOGD("cs[%d].name:%s requires bpfloader version [0x%05x,0x%05x)", i, name.c_str(),
- bpfMinVer, bpfMaxVer);
- if (bpfloader_ver < bpfMinVer) continue;
- if (bpfloader_ver >= bpfMaxVer) continue;
-
- if ((cs[i].prog_def->ignore_on_eng && isEng()) ||
- (cs[i].prog_def->ignore_on_user && isUser()) ||
- (cs[i].prog_def->ignore_on_userdebug && isUserdebug())) {
- ALOGD("cs[%d].name:%s is ignored on %s builds", i, name.c_str(),
- getBuildType().c_str());
- continue;
- }
-
- if ((isArm() && isKernel32Bit() && cs[i].prog_def->ignore_on_arm32) ||
- (isArm() && isKernel64Bit() && cs[i].prog_def->ignore_on_aarch64) ||
- (isX86() && isKernel32Bit() && cs[i].prog_def->ignore_on_x86_32) ||
- (isX86() && isKernel64Bit() && cs[i].prog_def->ignore_on_x86_64) ||
- (isRiscV() && cs[i].prog_def->ignore_on_riscv64)) {
- ALOGD("cs[%d].name:%s is ignored on %s", i, name.c_str(), describeArch());
- continue;
- }
-
- if (unrecognized(pin_subdir)) return -ENOTDIR;
-
- if (specified(selinux_context)) {
- ALOGI("prog %s selinux_context [%-32s] -> %d -> '%s' (%s)", name.c_str(),
- cs[i].prog_def->selinux_context, static_cast<int>(selinux_context),
- lookupSelinuxContext(selinux_context), lookupPinSubdir(selinux_context));
- }
-
- if (specified(pin_subdir)) {
- ALOGI("prog %s pin_subdir [%-32s] -> %d -> '%s'", name.c_str(),
- cs[i].prog_def->pin_subdir, static_cast<int>(pin_subdir),
- lookupPinSubdir(pin_subdir));
- }
-
- // strip any potential $foo suffix
- // this can be used to provide duplicate programs
- // conditionally loaded based on running kernel version
- name = name.substr(0, name.find_last_of('$'));
-
- bool reuse = false;
- // Format of pin location is
- // /sys/fs/bpf/<prefix>prog_<objName>_<progName>
- string progPinLoc = string(BPF_FS_PATH) + lookupPinSubdir(pin_subdir, prefix) + "prog_" +
- objName + '_' + string(name);
- if (access(progPinLoc.c_str(), F_OK) == 0) {
- fd.reset(retrieveProgram(progPinLoc.c_str()));
- ALOGD("New bpf prog load reusing prog %s, ret: %d (%s)", progPinLoc.c_str(), fd.get(),
- (!fd.ok() ? std::strerror(errno) : "no error"));
- reuse = true;
- } else {
- vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
-
- union bpf_attr req = {
- .prog_type = cs[i].type,
- .kern_version = kvers,
- .license = ptr_to_u64(license.c_str()),
- .insns = ptr_to_u64(cs[i].data.data()),
- .insn_cnt = static_cast<__u32>(cs[i].data.size() / sizeof(struct bpf_insn)),
- .log_level = 1,
- .log_buf = ptr_to_u64(log_buf.data()),
- .log_size = static_cast<__u32>(log_buf.size()),
- .expected_attach_type = cs[i].expected_attach_type,
- };
- if (isAtLeastKernelVersion(4, 15, 0))
- strlcpy(req.prog_name, cs[i].name.c_str(), sizeof(req.prog_name));
- fd.reset(bpf(BPF_PROG_LOAD, req));
-
- ALOGD("BPF_PROG_LOAD call for %s (%s) returned fd: %d (%s)", elfPath,
- cs[i].name.c_str(), fd.get(), (!fd.ok() ? std::strerror(errno) : "no error"));
-
- if (!fd.ok()) {
- vector<string> lines = android::base::Split(log_buf.data(), "\n");
-
- ALOGW("BPF_PROG_LOAD - BEGIN log_buf contents:");
- for (const auto& line : lines) ALOGW("%s", line.c_str());
- ALOGW("BPF_PROG_LOAD - END log_buf contents.");
-
- if (cs[i].prog_def->optional) {
- ALOGW("failed program is marked optional - continuing...");
- continue;
- }
- ALOGE("non-optional program failed to load.");
- }
- }
-
- if (!fd.ok()) return fd.get();
-
- if (!reuse) {
- if (specified(selinux_context)) {
- string createLoc = string(BPF_FS_PATH) + lookupPinSubdir(selinux_context) +
- "tmp_prog_" + objName + '_' + string(name);
- ret = bpfFdPin(fd, createLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", createLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- ret = renameat2(AT_FDCWD, createLoc.c_str(),
- AT_FDCWD, progPinLoc.c_str(), RENAME_NOREPLACE);
- if (ret) {
- int err = errno;
- ALOGE("rename %s %s -> %d [%d:%s]", createLoc.c_str(), progPinLoc.c_str(), ret,
- err, strerror(err));
- return -err;
- }
- } else {
- ret = bpfFdPin(fd, progPinLoc.c_str());
- if (ret) {
- int err = errno;
- ALOGE("create %s -> %d [%d:%s]", progPinLoc.c_str(), ret, err, strerror(err));
- return -err;
- }
- }
- if (chmod(progPinLoc.c_str(), 0440)) {
- int err = errno;
- ALOGE("chmod %s 0440 -> [%d:%s]", progPinLoc.c_str(), err, strerror(err));
- return -err;
- }
- if (chown(progPinLoc.c_str(), (uid_t)cs[i].prog_def->uid,
- (gid_t)cs[i].prog_def->gid)) {
- int err = errno;
- ALOGE("chown %s %d %d -> [%d:%s]", progPinLoc.c_str(), cs[i].prog_def->uid,
- cs[i].prog_def->gid, err, strerror(err));
- return -err;
- }
- }
-
- int progId = bpfGetFdProgId(fd);
- if (progId == -1) {
- ALOGE("bpfGetFdProgId failed, ret: %d [%d]", progId, errno);
- } else {
- ALOGI("prog %s id %d", progPinLoc.c_str(), progId);
- }
- }
-
- return 0;
-}
-
-int loadProg(const char* const elfPath, bool* const isCritical, const unsigned int bpfloader_ver,
- const Location& location) {
- vector<char> license;
- vector<char> critical;
- vector<codeSection> cs;
- vector<unique_fd> mapFds;
- int ret;
-
- if (!isCritical) return -1;
- *isCritical = false;
-
- ifstream elfFile(elfPath, ios::in | ios::binary);
- if (!elfFile.is_open()) return -1;
-
- ret = readSectionByName("critical", elfFile, critical);
- *isCritical = !ret;
-
- ret = readSectionByName("license", elfFile, license);
- if (ret) {
- ALOGE("Couldn't find license in %s", elfPath);
- return ret;
- } else {
- ALOGD("Loading %s%s ELF object %s with license %s",
- *isCritical ? "critical for " : "optional", *isCritical ? (char*)critical.data() : "",
- elfPath, (char*)license.data());
- }
-
- // the following default values are for bpfloader V0.0 format which does not include them
- unsigned int bpfLoaderMinVer =
- readSectionUint("bpfloader_min_ver", elfFile, DEFAULT_BPFLOADER_MIN_VER);
- unsigned int bpfLoaderMaxVer =
- readSectionUint("bpfloader_max_ver", elfFile, DEFAULT_BPFLOADER_MAX_VER);
- unsigned int bpfLoaderMinRequiredVer =
- readSectionUint("bpfloader_min_required_ver", elfFile, 0);
- size_t sizeOfBpfMapDef =
- readSectionUint("size_of_bpf_map_def", elfFile, DEFAULT_SIZEOF_BPF_MAP_DEF);
- size_t sizeOfBpfProgDef =
- readSectionUint("size_of_bpf_prog_def", elfFile, DEFAULT_SIZEOF_BPF_PROG_DEF);
-
- // inclusive lower bound check
- if (bpfloader_ver < bpfLoaderMinVer) {
- ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with min ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMinVer);
- return 0;
- }
-
- // exclusive upper bound check
- if (bpfloader_ver >= bpfLoaderMaxVer) {
- ALOGI("BpfLoader version 0x%05x ignoring ELF object %s with max ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMaxVer);
- return 0;
- }
-
- if (bpfloader_ver < bpfLoaderMinRequiredVer) {
- ALOGI("BpfLoader version 0x%05x failing due to ELF object %s with required min ver 0x%05x",
- bpfloader_ver, elfPath, bpfLoaderMinRequiredVer);
- return -1;
- }
-
- ALOGI("BpfLoader version 0x%05x processing ELF object %s with ver [0x%05x,0x%05x)",
- bpfloader_ver, elfPath, bpfLoaderMinVer, bpfLoaderMaxVer);
-
- if (sizeOfBpfMapDef < DEFAULT_SIZEOF_BPF_MAP_DEF) {
- ALOGE("sizeof(bpf_map_def) of %zu is too small (< %d)", sizeOfBpfMapDef,
- DEFAULT_SIZEOF_BPF_MAP_DEF);
- return -1;
- }
-
- if (sizeOfBpfProgDef < DEFAULT_SIZEOF_BPF_PROG_DEF) {
- ALOGE("sizeof(bpf_prog_def) of %zu is too small (< %d)", sizeOfBpfProgDef,
- DEFAULT_SIZEOF_BPF_PROG_DEF);
- return -1;
- }
-
- ret = readCodeSections(elfFile, cs, sizeOfBpfProgDef);
- if (ret) {
- ALOGE("Couldn't read all code sections in %s", elfPath);
- return ret;
- }
-
- /* Just for future debugging */
- if (0) dumpAllCs(cs);
-
- ret = createMaps(elfPath, elfFile, mapFds, location.prefix, sizeOfBpfMapDef, bpfloader_ver);
- if (ret) {
- ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
- return ret;
- }
-
- for (int i = 0; i < (int)mapFds.size(); i++)
- ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
-
- applyMapRelo(elfFile, mapFds, cs);
-
- ret = loadCodeSections(elfPath, cs, string(license.data()), location.prefix, bpfloader_ver);
- if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
-
- return ret;
-}
-
-} // namespace bpf
-} // namespace android
diff --git a/netbpfload/loader.h b/netbpfload/loader.h
deleted file mode 100644
index 4da6830..0000000
--- a/netbpfload/loader.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2018-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.
- */
-
-#pragma once
-
-#include <linux/bpf.h>
-
-#include <fstream>
-
-namespace android {
-namespace bpf {
-
-// Bpf programs may specify per-program & per-map selinux_context and pin_subdir.
-//
-// The BpfLoader needs to convert these bpf.o specified strings into an enum
-// for internal use (to check that valid values were specified for the specific
-// location of the bpf.o file).
-//
-// It also needs to map selinux_context's into pin_subdir's.
-// This is because of how selinux_context is actually implemented via pin+rename.
-//
-// Thus 'domain' enumerates all selinux_context's/pin_subdir's that the BpfLoader
-// is aware of. Thus there currently needs to be a 1:1 mapping between the two.
-//
-enum class domain : int {
- unrecognized = -1, // invalid for this version of the bpfloader
- unspecified = 0, // means just use the default for that specific pin location
- tethering, // (S+) fs_bpf_tethering /sys/fs/bpf/tethering
- net_private, // (T+) fs_bpf_net_private /sys/fs/bpf/net_private
- net_shared, // (T+) fs_bpf_net_shared /sys/fs/bpf/net_shared
- netd_readonly, // (T+) fs_bpf_netd_readonly /sys/fs/bpf/netd_readonly
- netd_shared, // (T+) fs_bpf_netd_shared /sys/fs/bpf/netd_shared
-};
-
-// Note: this does not include domain::unrecognized, but does include domain::unspecified
-static constexpr domain AllDomains[] = {
- domain::unspecified,
- domain::tethering,
- domain::net_private,
- domain::net_shared,
- domain::netd_readonly,
- domain::netd_shared,
-};
-
-static constexpr bool unrecognized(domain d) {
- return d == domain::unrecognized;
-}
-
-// Note: this doesn't handle unrecognized, handle it first.
-static constexpr bool specified(domain d) {
- return d != domain::unspecified;
-}
-
-struct Location {
- const char* const dir = "";
- const char* const prefix = "";
-};
-
-// BPF loader implementation. Loads an eBPF ELF object
-int loadProg(const char* elfPath, bool* isCritical, const unsigned int bpfloader_ver,
- const Location &location = {});
-
-// Exposed for testing
-unsigned int readSectionUint(const char* name, std::ifstream& elfFile, unsigned int defVal);
-
-// Returns the build type string (from ro.build.type).
-const std::string& getBuildType();
-
-// The following functions classify the 3 Android build types.
-inline bool isEng() {
- return getBuildType() == "eng";
-}
-inline bool isUser() {
- return getBuildType() == "user";
-}
-inline bool isUserdebug() {
- return getBuildType() == "userdebug";
-}
-
-} // namespace bpf
-} // namespace android
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index 419ec3a..5f672e7 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1936,6 +1936,8 @@
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
.setIsQueryWithKnownAnswerEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_QUERY_WITH_KNOWN_ANSWER))
+ .setAvoidAdvertisingEmptyTxtRecords(mDeps.isTetheringFeatureNotChickenedOut(
+ mContext, MdnsFeatureFlags.NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS))
.setOverrideProvider(flag -> mDeps.isFeatureEnabled(
mContext, FORCE_ENABLE_FLAG_FOR_TEST_PREFIX + flag))
.build();
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
index c264f25..709dc79 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -67,6 +67,12 @@
*/
public static final String NSD_QUERY_WITH_KNOWN_ANSWER = "nsd_query_with_known_answer";
+ /**
+ * A feature flag to avoid advertising empty TXT records, as per RFC 6763 6.1.
+ */
+ public static final String NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS =
+ "nsd_avoid_advertising_empty_txt_records";
+
// Flag for offload feature
public final boolean mIsMdnsOffloadFeatureEnabled;
@@ -91,6 +97,9 @@
// Flag for query with known-answer
public final boolean mIsQueryWithKnownAnswerEnabled;
+ // Flag for avoiding advertising empty TXT records
+ public final boolean mAvoidAdvertisingEmptyTxtRecords;
+
@Nullable
private final FlagOverrideProvider mOverrideProvider;
@@ -142,6 +151,15 @@
}
/**
+ * Indicates whether {@link #NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS} is enabled, including for
+ * testing.
+ */
+ public boolean avoidAdvertisingEmptyTxtRecords() {
+ return mAvoidAdvertisingEmptyTxtRecords
+ || isForceEnabledForTest(NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS);
+ }
+
+ /**
* The constructor for {@link MdnsFeatureFlags}.
*/
public MdnsFeatureFlags(boolean isOffloadFeatureEnabled,
@@ -152,6 +170,7 @@
boolean isUnicastReplyEnabled,
boolean isAggressiveQueryModeEnabled,
boolean isQueryWithKnownAnswerEnabled,
+ boolean avoidAdvertisingEmptyTxtRecords,
@Nullable FlagOverrideProvider overrideProvider) {
mIsMdnsOffloadFeatureEnabled = isOffloadFeatureEnabled;
mIncludeInetAddressRecordsInProbing = includeInetAddressRecordsInProbing;
@@ -161,6 +180,7 @@
mIsUnicastReplyEnabled = isUnicastReplyEnabled;
mIsAggressiveQueryModeEnabled = isAggressiveQueryModeEnabled;
mIsQueryWithKnownAnswerEnabled = isQueryWithKnownAnswerEnabled;
+ mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords;
mOverrideProvider = overrideProvider;
}
@@ -181,6 +201,7 @@
private boolean mIsUnicastReplyEnabled;
private boolean mIsAggressiveQueryModeEnabled;
private boolean mIsQueryWithKnownAnswerEnabled;
+ private boolean mAvoidAdvertisingEmptyTxtRecords;
private FlagOverrideProvider mOverrideProvider;
/**
@@ -195,6 +216,7 @@
mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
+ mAvoidAdvertisingEmptyTxtRecords = true; // Default enabled.
mOverrideProvider = null;
}
@@ -291,6 +313,16 @@
}
/**
+ * Set whether to avoid advertising empty TXT records.
+ *
+ * @see #NSD_AVOID_ADVERTISING_EMPTY_TXT_RECORDS
+ */
+ public Builder setAvoidAdvertisingEmptyTxtRecords(boolean avoidAdvertisingEmptyTxtRecords) {
+ mAvoidAdvertisingEmptyTxtRecords = avoidAdvertisingEmptyTxtRecords;
+ return this;
+ }
+
+ /**
* Builds a {@link MdnsFeatureFlags} with the arguments supplied to this builder.
*/
public MdnsFeatureFlags build() {
@@ -302,6 +334,7 @@
mIsUnicastReplyEnabled,
mIsAggressiveQueryModeEnabled,
mIsQueryWithKnownAnswerEnabled,
+ mAvoidAdvertisingEmptyTxtRecords,
mOverrideProvider);
}
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
index 0e84764..ebd95c9 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsRecordRepository.java
@@ -227,11 +227,13 @@
/**
* Create a ServiceRegistration with only update the subType.
*/
- ServiceRegistration withSubtypes(@NonNull Set<String> newSubtypes) {
+ ServiceRegistration withSubtypes(@NonNull Set<String> newSubtypes,
+ boolean avoidEmptyTxtRecords) {
NsdServiceInfo newServiceInfo = new NsdServiceInfo(serviceInfo);
newServiceInfo.setSubtypes(newSubtypes);
return new ServiceRegistration(srvRecord.record.getServiceHost(), newServiceInfo,
- repliedServiceCount, sentPacketCount, exiting, isProbing, ttl);
+ repliedServiceCount, sentPacketCount, exiting, isProbing, ttl,
+ avoidEmptyTxtRecords);
}
/**
@@ -239,7 +241,7 @@
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
int repliedServiceCount, int sentPacketCount, boolean exiting, boolean isProbing,
- @Nullable Duration ttl) {
+ @Nullable Duration ttl, boolean avoidEmptyTxtRecords) {
this.serviceInfo = serviceInfo;
final long nonNameRecordsTtlMillis;
@@ -310,7 +312,8 @@
// Service name is verified unique after probing
true /* cacheFlush */,
nonNameRecordsTtlMillis,
- attrsToTextEntries(serviceInfo.getAttributes())),
+ attrsToTextEntries(
+ serviceInfo.getAttributes(), avoidEmptyTxtRecords)),
false /* sharedName */);
allRecords.addAll(ptrRecords);
@@ -393,9 +396,10 @@
* @param serviceInfo Service to advertise
*/
ServiceRegistration(@NonNull String[] deviceHostname, @NonNull NsdServiceInfo serviceInfo,
- int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl) {
+ int repliedServiceCount, int sentPacketCount, @Nullable Duration ttl,
+ boolean avoidEmptyTxtRecords) {
this(deviceHostname, serviceInfo,repliedServiceCount, sentPacketCount,
- false /* exiting */, true /* isProbing */, ttl);
+ false /* exiting */, true /* isProbing */, ttl, avoidEmptyTxtRecords);
}
void setProbing(boolean probing) {
@@ -446,7 +450,7 @@
"Service ID must already exist for an update request: " + serviceId);
}
final ServiceRegistration updatedRegistration = existingRegistration.withSubtypes(
- subtypes);
+ subtypes, mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
mServices.put(serviceId, updatedRegistration);
}
@@ -477,7 +481,8 @@
final ServiceRegistration registration = new ServiceRegistration(
mDeviceHostname, serviceInfo, NO_PACKET /* repliedServiceCount */,
- NO_PACKET /* sentPacketCount */, ttl);
+ NO_PACKET /* sentPacketCount */, ttl,
+ mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
mServices.put(serviceId, registration);
// Remove existing exiting service
@@ -548,8 +553,17 @@
return new MdnsProber.ProbingInfo(serviceId, probingRecords);
}
- private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(Map<String, byte[]> attrs) {
- final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(attrs.size());
+ private static List<MdnsServiceInfo.TextEntry> attrsToTextEntries(Map<String, byte[]> attrs,
+ boolean avoidEmptyTxtRecords) {
+ final List<MdnsServiceInfo.TextEntry> out = new ArrayList<>(
+ attrs.size() == 0 ? 1 : attrs.size());
+ if (avoidEmptyTxtRecords && attrs.size() == 0) {
+ // As per RFC6763 6.1, empty TXT records are not allowed, but records containing a
+ // single empty String must be treated as equivalent.
+ out.add(new MdnsServiceInfo.TextEntry("", (byte[]) null));
+ return out;
+ }
+
for (Map.Entry<String, byte[]> attr : attrs.entrySet()) {
out.add(new MdnsServiceInfo.TextEntry(attr.getKey(), attr.getValue()));
}
@@ -1403,7 +1417,8 @@
if (existing == null) return null;
final ServiceRegistration newService = new ServiceRegistration(mDeviceHostname, newInfo,
- existing.repliedServiceCount, existing.sentPacketCount, existing.ttl);
+ existing.repliedServiceCount, existing.sentPacketCount, existing.ttl,
+ mMdnsFeatureFlags.avoidAdvertisingEmptyTxtRecords());
mServices.put(serviceId, newService);
return makeProbingInfo(serviceId, newService);
}
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
index 7eea93a..a8a4ef1 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceCache.java
@@ -233,6 +233,21 @@
}
/**
+ * Remove services which matches the given type and socket.
+ *
+ * @param cacheKey the target CacheKey.
+ */
+ public void removeServices(@NonNull CacheKey cacheKey) {
+ ensureRunningOnHandlerThread(mHandler);
+ // Remove all services
+ if (mCachedServices.remove(cacheKey) == null) {
+ return;
+ }
+ // Update the next expiration check time if services are removed.
+ mNextExpirationTime = getNextExpirationTime(mClock.elapsedRealtime());
+ }
+
+ /**
* Register a callback to listen to service expiration.
*
* <p> Registering the same callback instance twice is a no-op, since MdnsServiceTypeClient
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
index 1ec9e39..a16fcf7 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsServiceInfo.java
@@ -16,8 +16,6 @@
package com.android.server.connectivity.mdns;
-import static com.android.server.connectivity.mdns.MdnsSocket.INTERFACE_INDEX_UNSPECIFIED;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Network;
@@ -33,7 +31,6 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@@ -355,6 +352,13 @@
/** Represents a DNS TXT key-value pair defined by RFC 6763. */
public static final class TextEntry implements Parcelable {
+ /**
+ * The value to use for attributes with no value.
+ *
+ * <p>As per RFC6763 P.16, attributes may have no value, which is different from having an
+ * empty value (which would be an empty byte array).
+ */
+ public static final byte[] VALUE_NONE = null;
public static final Parcelable.Creator<TextEntry> CREATOR =
new Parcelable.Creator<TextEntry>() {
@Override
@@ -389,7 +393,7 @@
// 2. If there is no '=' in a DNS-SD TXT record string, then it is a
// boolean attribute, simply identified as being present, with no value.
if (delimitPos < 0) {
- return new TextEntry(new String(textBytes, US_ASCII), (byte[]) null);
+ return new TextEntry(new String(textBytes, US_ASCII), VALUE_NONE);
} else if (delimitPos == 0) {
return null;
}
@@ -400,13 +404,13 @@
/** Creates a new {@link TextEntry} with given key and value of a UTF-8 string. */
public TextEntry(String key, String value) {
- this(key, value == null ? null : value.getBytes(UTF_8));
+ this(key, value == null ? VALUE_NONE : value.getBytes(UTF_8));
}
/** Creates a new {@link TextEntry} with given key and value of a byte array. */
public TextEntry(String key, byte[] value) {
this.key = key;
- this.value = value == null ? null : value.clone();
+ this.value = value == VALUE_NONE ? VALUE_NONE : value.clone();
}
private TextEntry(Parcel in) {
@@ -419,22 +423,26 @@
}
public byte[] getValue() {
- return value == null ? null : value.clone();
+ return value == VALUE_NONE ? VALUE_NONE : value.clone();
}
/** Converts this {@link TextEntry} instance to '=' separated byte array. */
public byte[] toBytes() {
final byte[] keyBytes = key.getBytes(US_ASCII);
- if (value == null) {
+ if (value == VALUE_NONE) {
return keyBytes;
}
return ByteUtils.concat(keyBytes, new byte[]{'='}, value);
}
+ public boolean isEmpty() {
+ return TextUtils.isEmpty(key) && (value == VALUE_NONE || value.length == 0);
+ }
+
/** Converts this {@link TextEntry} instance to '=' separated string. */
@Override
public String toString() {
- if (value == null) {
+ if (value == VALUE_NONE) {
return key;
}
return key + "=" + new String(value, UTF_8);
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
index 92cf324..77d1d7a 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsTextRecord.java
@@ -89,6 +89,13 @@
}
}
+ private boolean isEmpty() {
+ return entries == null || entries.size() == 0
+ // RFC6763 6.1 indicates that a TXT record with a single zero byte is equivalent to
+ // an empty record.
+ || (entries.size() == 1 && entries.get(0).isEmpty());
+ }
+
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@@ -105,7 +112,7 @@
@Override
public int hashCode() {
- return (super.hashCode() * 31) + Objects.hash(entries);
+ return (super.hashCode() * 31) + (isEmpty() ? 0 : Objects.hash(entries));
}
@Override
@@ -116,7 +123,19 @@
if (!(other instanceof MdnsTextRecord)) {
return false;
}
-
- return super.equals(other) && Objects.equals(entries, ((MdnsTextRecord) other).entries);
+ if (!super.equals(other)) {
+ return false;
+ }
+ // As per RFC6763 6.1: DNS-SD clients MUST treat the following as equivalent:
+ // - A TXT record containing a single zero byte.
+ // - An empty (zero-length) TXT record. (This is not strictly legal, but should one be
+ // received, it should be interpreted as the same as a single empty string.)
+ // - No TXT record
+ // Ensure that empty TXT records are considered equal, so that they are not considered
+ // conflicting for example.
+ if (isEmpty() && ((MdnsTextRecord) other).isEmpty()) {
+ return true;
+ }
+ return Objects.equals(entries, ((MdnsTextRecord) other).entries);
}
}
\ No newline at end of file
diff --git a/service/src/com/android/server/CallbackQueue.java b/service/src/com/android/server/CallbackQueue.java
index 060a984..4e068ea 100644
--- a/service/src/com/android/server/CallbackQueue.java
+++ b/service/src/com/android/server/CallbackQueue.java
@@ -34,8 +34,7 @@
* queue.forEach(netId, callbackId -> { [...] });
* queue.addCallback(netId, callbackId);
* [...]
- * queue.shrinkToLength();
- * storedCallbacks = queue.getBackingArray();
+ * storedCallbacks = queue.getMinimizedBackingArray();
* </pre>
*
* <p>This class is not thread-safe.
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 953fd76..0fe24a2 100755
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -56,6 +56,7 @@
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
+import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_ALL;
import static android.net.ConnectivityManager.NetworkCallback.DECLARED_METHODS_NONE;
import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
@@ -2147,7 +2148,7 @@
}
@VisibleForTesting
- void updateMobileDataPreferredUids() {
+ public void updateMobileDataPreferredUids() {
mHandler.sendEmptyMessage(EVENT_MOBILE_DATA_PREFERRED_UIDS_CHANGED);
}
@@ -3403,7 +3404,7 @@
}
@VisibleForTesting
- void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
+ public void handleBlockedReasonsChanged(List<Pair<Integer, Integer>> reasonsList) {
for (Pair<Integer, Integer> reasons: reasonsList) {
final int uid = reasons.first;
final int blockedReasons = reasons.second;
@@ -3472,7 +3473,7 @@
private void handleFrozenUids(int[] uids, int[] frozenStates) {
ensureRunningOnConnectivityServiceThread();
handleDestroyFrozenSockets(uids, frozenStates);
- // TODO: handle freezing NetworkCallbacks
+ handleFreezeNetworkCallbacks(uids, frozenStates);
}
private void handleDestroyFrozenSockets(int[] uids, int[] frozenStates) {
@@ -3490,6 +3491,73 @@
}
}
+ private void handleFreezeNetworkCallbacks(int[] uids, int[] frozenStates) {
+ if (!mQueueCallbacksForFrozenApps) {
+ return;
+ }
+ for (int i = 0; i < uids.length; i++) {
+ final int uid = uids[i];
+ // These counters may be modified on different threads, but using them here is fine
+ // because this is only an optimization where wrong behavior would only happen if they
+ // are zero even though there is a request registered. This is not possible as they are
+ // always incremented before posting messages to register, and decremented on the
+ // handler thread when unregistering.
+ if (mSystemNetworkRequestCounter.get(uid) == 0
+ && mNetworkRequestCounter.get(uid) == 0) {
+ // Avoid iterating requests if there isn't any. The counters only track app requests
+ // and not internal requests (for example always-on requests which do not have a
+ // mMessenger), so it does not completely match the content of mRequests. This is OK
+ // as only app requests need to be frozen.
+ continue;
+ }
+
+ if (frozenStates[i] == UID_FROZEN_STATE_FROZEN) {
+ freezeNetworkCallbacksForUid(uid);
+ } else {
+ unfreezeNetworkCallbacksForUid(uid);
+ }
+ }
+ }
+
+ /**
+ * Suspend callbacks for a UID that was just frozen.
+ *
+ * <p>Note that it is not possible for a process to be frozen during a blocking binder call
+ * (see CachedAppOptimizer.freezeBinder), and IConnectivityManager callback registrations are
+ * blocking binder calls, so no callback can be registered while the UID is frozen. This means
+ * it is not necessary to check frozen state on new callback registrations, and calling this
+ * method when a UID is newly frozen is sufficient.
+ *
+ * <p>If it ever becomes possible for a process to be frozen during a blocking binder call,
+ * ConnectivityService will need to handle freezing callbacks that reach ConnectivityService
+ * after the app was frozen when being registered.
+ */
+ private void freezeNetworkCallbacksForUid(int uid) {
+ if (DDBG) Log.d(TAG, "Freezing callbacks for UID " + uid);
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.mUid != uid) continue;
+ // mNetworkRequests can have duplicate values for multilayer requests, but calling
+ // onFrozen multiple times is fine.
+ // If freezeNetworkCallbacksForUid was called multiple times in a raw for a frozen UID
+ // (which would be incorrect), this would also handle it gracefully.
+ nri.onFrozen();
+ }
+ }
+
+ private void unfreezeNetworkCallbacksForUid(int uid) {
+ // This sends all callbacks for one NetworkRequest at a time, which may not be the
+ // same order they were queued in, but different network requests use different
+ // binder objects, so the relative order of their callbacks is not guaranteed.
+ // If callbacks are not queued, callbacks from different binder objects may be
+ // posted on different threads when the process is unfrozen, so even if they were
+ // called a long time apart while the process was frozen, they may still appear in
+ // different order when unfreezing it.
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.mUid != uid) continue;
+ nri.sendQueuedCallbacks();
+ }
+ }
+
private void handleUpdateFirewallDestroySocketReasons(
List<Pair<Integer, Integer>> reasonsList) {
if (!shouldTrackFirewallDestroySocketReasons()) {
@@ -7544,6 +7612,29 @@
// single NetworkRequest in mRequests.
final List<NetworkRequest> mRequests;
+ /**
+ * List of callbacks that are queued for sending later when the requesting app is unfrozen.
+ *
+ * <p>There may typically be hundreds of NetworkRequestInfo, so a memory-efficient structure
+ * (just an int[]) is used to keep queued callbacks. This reduces the number of object
+ * references.
+ *
+ * <p>This is intended to be used with {@link CallbackQueue} which defines the internal
+ * format.
+ */
+ @NonNull
+ private int[] mQueuedCallbacks = new int[0];
+
+ private static final int MATCHED_NETID_NOT_FROZEN = -1;
+
+ /**
+ * If this request was already satisfied by a network when the requesting UID was frozen,
+ * the netId that was matched at that time. Otherwise, NETID_UNSET if no network was
+ * satisfying this request when frozen (including if this is a listen and not a request),
+ * and MATCHED_NETID_NOT_FROZEN if not frozen.
+ */
+ private int mMatchedNetIdWhenFrozen = MATCHED_NETID_NOT_FROZEN;
+
// mSatisfier and mActiveRequest rely on one another therefore set them together.
void setSatisfier(
@Nullable final NetworkAgentInfo satisfier,
@@ -7715,6 +7806,8 @@
}
setSatisfier(satisfier, activeRequest);
}
+ mMatchedNetIdWhenFrozen = nri.mMatchedNetIdWhenFrozen;
+ mQueuedCallbacks = nri.mQueuedCallbacks;
mMessenger = nri.mMessenger;
mBinder = nri.mBinder;
mPid = nri.mPid;
@@ -7779,11 +7872,190 @@
}
}
+ /**
+ * Called when this NRI is being frozen.
+ *
+ * <p>Calling this method multiple times when the NRI is frozen is fine. This may happen
+ * if iterating through the NetworkRequest -> NRI map since there are duplicates in the
+ * NRI values for multilayer requests. It may also happen if an app is frozen, killed,
+ * restarted and refrozen since there is no callback sent when processes are killed, but in
+ * that case the callbacks to the killed app do not matter.
+ */
+ void onFrozen() {
+ if (mMatchedNetIdWhenFrozen != MATCHED_NETID_NOT_FROZEN) {
+ // Already frozen
+ return;
+ }
+ if (mSatisfier != null) {
+ mMatchedNetIdWhenFrozen = mSatisfier.network.netId;
+ } else {
+ mMatchedNetIdWhenFrozen = NETID_UNSET;
+ }
+ }
+
+ boolean maybeQueueCallback(@NonNull NetworkAgentInfo nai, int callbackId) {
+ if (mMatchedNetIdWhenFrozen == MATCHED_NETID_NOT_FROZEN) {
+ return false;
+ }
+
+ boolean ignoreThisCallback = false;
+ final int netId = nai.network.netId;
+ final CallbackQueue queue = new CallbackQueue(mQueuedCallbacks);
+ // Based on the new callback, clear previous callbacks that are no longer necessary.
+ // For example, if the network is lost, there is no need to send intermediate callbacks.
+ switch (callbackId) {
+ // PRECHECK is not an API and not very meaningful, do not deliver it for frozen apps
+ // Networks are likely to already be lost when the app is unfrozen, also skip LOSING
+ case CALLBACK_PRECHECK:
+ case CALLBACK_LOSING:
+ ignoreThisCallback = true;
+ break;
+ case CALLBACK_LOST:
+ // All callbacks for this netId before onLost are unnecessary. And onLost itself
+ // is also unnecessary if onAvailable was previously queued for this netId: the
+ // Network just appeared and disappeared while the app was frozen.
+ ignoreThisCallback = queue.hasCallback(netId, CALLBACK_AVAILABLE);
+ queue.removeCallbacksForNetId(netId);
+ break;
+ case CALLBACK_AVAILABLE:
+ if (mSatisfier != null) {
+ // For requests that are satisfied by individual networks (not LISTEN), when
+ // AVAILABLE is received, the request is matching a new Network, so previous
+ // callbacks (for other Networks) are unnecessary.
+ queue.clear();
+ }
+ break;
+ case CALLBACK_SUSPENDED:
+ case CALLBACK_RESUMED:
+ if (queue.hasCallback(netId, CALLBACK_AVAILABLE)) {
+ // AVAILABLE will already send the latest suspended status
+ ignoreThisCallback = true;
+ break;
+ }
+ // If SUSPENDED was queued, just remove it from the queue instead of sending
+ // RESUMED; and vice-versa.
+ final int otherCb = callbackId == CALLBACK_SUSPENDED
+ ? CALLBACK_RESUMED
+ : CALLBACK_SUSPENDED;
+ ignoreThisCallback = queue.removeCallbacks(netId, otherCb);
+ break;
+ case CALLBACK_CAP_CHANGED:
+ case CALLBACK_IP_CHANGED:
+ case CALLBACK_LOCAL_NETWORK_INFO_CHANGED:
+ case CALLBACK_BLK_CHANGED:
+ ignoreThisCallback = queue.hasCallback(netId, CALLBACK_AVAILABLE);
+ break;
+ default:
+ Log.wtf(TAG, "Unexpected callback type: "
+ + ConnectivityManager.getCallbackName(callbackId));
+ return false;
+ }
+
+ if (!ignoreThisCallback) {
+ // For non-listen (matching) callbacks, AVAILABLE can appear in the queue twice in a
+ // row for the same network if the new AVAILABLE suppressed intermediate AVAILABLEs
+ // for other networks. Example:
+ // A is matched, app is frozen, B is matched, A is matched again (removes callbacks
+ // for B), app is unfrozen.
+ // In that case call AVAILABLE sub-callbacks to update state, but not AVAILABLE
+ // itself.
+ if (callbackId == CALLBACK_AVAILABLE && netId == mMatchedNetIdWhenFrozen) {
+ // The queue should have been cleared here, since this is AVAILABLE on a
+ // non-listen callback (mMatchedNetIdWhenFrozen is set).
+ addAvailableSubCallbacks(nai, queue);
+ } else {
+ // When unfreezing, no need to send a callback multiple times for the same netId
+ queue.removeCallbacks(netId, callbackId);
+ // TODO: this code always adds the callback for simplicity. It would save
+ // some CPU/memory if the code instead only added to the queue callbacks where
+ // isCallbackOverridden=true, or which need to be in the queue because they
+ // affect other callbacks that are overridden.
+ queue.addCallback(netId, callbackId);
+ }
+ }
+ // Instead of shrinking the queue, possibly reallocating, the NRI could keep the array
+ // and length in memory for future adds, but this saves memory by avoiding the cost
+ // of an extra member and of unused array length (there are often hundreds of NRIs).
+ mQueuedCallbacks = queue.getMinimizedBackingArray();
+ return true;
+ }
+
+ /**
+ * Called when this NRI is being unfrozen to stop queueing, and send queued callbacks.
+ *
+ * <p>Calling this method multiple times when the NRI is unfrozen (for example iterating
+ * through the NetworkRequest -> NRI map where there are duplicate values for multilayer
+ * requests) is fine.
+ */
+ void sendQueuedCallbacks() {
+ mMatchedNetIdWhenFrozen = MATCHED_NETID_NOT_FROZEN;
+ if (mQueuedCallbacks.length == 0) {
+ return;
+ }
+ new CallbackQueue(mQueuedCallbacks).forEach((netId, callbackId) -> {
+ // For CALLBACK_LOST only, there will not be a NAI for the netId. Build and send the
+ // callback directly.
+ if (callbackId == CALLBACK_LOST) {
+ if (isCallbackOverridden(CALLBACK_LOST)) {
+ final Bundle cbBundle = makeCommonBundleForCallback(this,
+ new Network(netId));
+ callCallbackForRequest(this, CALLBACK_LOST, cbBundle, 0 /* arg1 */);
+ }
+ return; // Next item in forEach
+ }
+
+ // Other callbacks should always have a NAI, because if a Network disconnects
+ // LOST will be called, unless the request is no longer satisfied by that Network in
+ // which case AVAILABLE will have been called for another Network. In both cases
+ // previous callbacks are cleared.
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ if (nai == null) {
+ Log.wtf(TAG, "Missing NetworkAgentInfo for net " + netId
+ + " for callback " + callbackId);
+ return; // Next item in forEach
+ }
+
+ final int arg1 =
+ callbackId == CALLBACK_AVAILABLE || callbackId == CALLBACK_BLK_CHANGED
+ ? getBlockedState(nai, mAsUid)
+ : 0;
+ callCallbackForRequest(this, nai, callbackId, arg1);
+ });
+ mQueuedCallbacks = new int[0];
+ }
+
boolean isCallbackOverridden(int callbackId) {
return !mUseDeclaredMethodsForCallbacksEnabled
|| (mDeclaredMethodsFlags & (1 << callbackId)) != 0;
}
+ /**
+ * Queue all callbacks that are called by AVAILABLE, except onAvailable.
+ *
+ * <p>AVAILABLE may call SUSPENDED, CAP_CHANGED, IP_CHANGED, LOCAL_NETWORK_INFO_CHANGED,
+ * and BLK_CHANGED, in this order.
+ */
+ private void addAvailableSubCallbacks(
+ @NonNull NetworkAgentInfo nai, @NonNull CallbackQueue queue) {
+ final boolean callSuspended =
+ !nai.networkCapabilities.hasCapability(NET_CAPABILITY_NOT_SUSPENDED);
+ final boolean callLocalInfoChanged = nai.isLocalNetwork();
+
+ final int cbCount = 3 + (callSuspended ? 1 : 0) + (callLocalInfoChanged ? 1 : 0);
+ // Avoid unnecessary re-allocations by reserving enough space for all callbacks to add.
+ queue.ensureHasCapacity(cbCount);
+ final int netId = nai.network.netId;
+ if (callSuspended) {
+ queue.addCallback(netId, CALLBACK_SUSPENDED);
+ }
+ queue.addCallback(netId, CALLBACK_CAP_CHANGED);
+ queue.addCallback(netId, CALLBACK_IP_CHANGED);
+ if (callLocalInfoChanged) {
+ queue.addCallback(netId, CALLBACK_LOCAL_NETWORK_INFO_CHANGED);
+ }
+ queue.addCallback(netId, CALLBACK_BLK_CHANGED);
+ }
+
boolean hasHigherOrderThan(@NonNull final NetworkRequestInfo target) {
// Compare two preference orders.
return mPreferenceOrder < target.mPreferenceOrder;
@@ -9400,8 +9672,10 @@
* interfaces.
* Ingress discard rule is added to the address iff
* 1. The address is not a link local address
- * 2. The address is used by a single VPN interface and not used by any other
- * interfaces even non-VPN ones
+ * 2. The address is used by a single interface of VPN whose VPN type is not TYPE_VPN_LEGACY
+ * or TYPE_VPN_OEM and the address is not used by any other interfaces even non-VPN ones
+ * Ingress discard rule is not be added to TYPE_VPN_LEGACY or TYPE_VPN_OEM VPN since these VPNs
+ * might need to receive packet to VPN address via non-VPN interface.
* This method can be called during network disconnects, when nai has already been removed from
* mNetworkAgentInfos.
*
@@ -9436,7 +9710,10 @@
// for different network.
final Set<Pair<InetAddress, String>> ingressDiscardRules = new ArraySet<>();
for (final NetworkAgentInfo agent : nais) {
- if (!agent.isVPN() || agent.isDestroyed()) {
+ final int vpnType = getVpnType(agent);
+ if (!agent.isVPN() || agent.isDestroyed()
+ || vpnType == VpnManager.TYPE_VPN_LEGACY
+ || vpnType == VpnManager.TYPE_VPN_OEM) {
continue;
}
final LinkProperties agentLp = (nai == agent) ? lp : agent.linkProperties;
@@ -10277,6 +10554,11 @@
// are Type.LISTEN, but should not have NetworkCallbacks invoked.
return;
}
+ // Even if a callback ends up not being sent, it may affect other callbacks in the queue, so
+ // queue callbacks before checking the declared methods flags.
+ if (networkAgent != null && nri.maybeQueueCallback(networkAgent, notificationType)) {
+ return;
+ }
if (!nri.isCallbackOverridden(notificationType)) {
// No need to send the notification as the recipient method is not overridden
return;
diff --git a/service/src/com/android/server/connectivity/NetworkDiagnostics.java b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
index 3db37e5..8a2e72c 100644
--- a/service/src/com/android/server/connectivity/NetworkDiagnostics.java
+++ b/service/src/com/android/server/connectivity/NetworkDiagnostics.java
@@ -567,7 +567,9 @@
@Override
public void close() {
- IoUtils.closeQuietly(mFileDescriptor);
+ if (mFileDescriptor != null) {
+ IoUtils.closeQuietly(mFileDescriptor);
+ }
}
}
@@ -611,6 +613,7 @@
setupSocket(SOCK_DGRAM, mProtocol, TIMEOUT_SEND, TIMEOUT_RECV, 0);
} catch (ErrnoException | IOException e) {
mMeasurement.recordFailure(e.toString());
+ close();
return;
}
mMeasurement.description += " src{" + socketAddressToString(mSocketAddress) + "}";
@@ -695,6 +698,7 @@
NetworkConstants.DNS_SERVER_PORT);
} catch (ErrnoException | IOException e) {
mMeasurement.recordFailure(e.toString());
+ close();
return;
}
diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp
index c9c8be9..ed0670d 100644
--- a/staticlibs/Android.bp
+++ b/staticlibs/Android.bp
@@ -58,13 +58,8 @@
"//apex_available:platform",
],
visibility: [
- "//frameworks/base/packages/Tethering",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/Connectivity/framework:__subpackages__",
- "//frameworks/opt/net/ike",
- "//frameworks/opt/net/wifi/service",
- "//packages/modules/Wifi/service",
- "//frameworks/opt/net/telephony",
"//packages/modules/NetworkStack:__subpackages__",
"//packages/modules/CaptivePortalLogin",
],
@@ -455,6 +450,7 @@
visibility: ["//packages/modules/Connectivity/service-t"],
}
+// net-utils-framework-connectivity is only for framework-connectivity.
java_library {
name: "net-utils-framework-connectivity",
srcs: [
@@ -467,8 +463,7 @@
"//apex_available:platform",
],
visibility: [
- "//packages/modules/Connectivity:__subpackages__",
- "//packages/modules/NetworkStack:__subpackages__",
+ "//packages/modules/Connectivity/framework",
],
libs: [
"androidx.annotation_annotation",
@@ -486,7 +481,6 @@
name: "net-utils-non-bootclasspath-defaults",
sdk_version: "module_current",
min_sdk_version: "30",
- jarjar_rules: "jarjar-rules-shared.txt",
libs: [
"androidx.annotation_annotation",
"framework-annotations-lib",
@@ -508,9 +502,6 @@
"com.android.tethering",
"//apex_available:platform",
],
- visibility: [
- "//packages/modules/Connectivity:__subpackages__",
- ],
defaults_visibility: [
"//visibility:private",
],
@@ -520,6 +511,7 @@
},
}
+// net-utils-service-connectivity is only for service-connectivity.
java_library {
name: "net-utils-service-connectivity",
srcs: [
@@ -532,15 +524,25 @@
"net-utils-framework-connectivity",
],
defaults: ["net-utils-non-bootclasspath-defaults"],
+ jarjar_rules: "jarjar-rules-shared.txt",
+ visibility: [
+ "//packages/modules/Connectivity/service",
+ "//packages/modules/Connectivity/staticlibs/tests/unit",
+ ],
}
java_library {
- name: "net-utils-tethering",
+ name: "net-utils-connectivity-apks",
srcs: [
":net-utils-all-srcs",
":framework-connectivity-shared-srcs",
],
defaults: ["net-utils-non-bootclasspath-defaults"],
+ jarjar_rules: "jarjar-rules-shared.txt",
+ visibility: [
+ "//packages/modules/CaptivePortalLogin:__subpackages__",
+ "//packages/modules/Connectivity/Tethering",
+ ],
}
aidl_interface {
@@ -555,6 +557,8 @@
min_sdk_version: "30",
apex_available: [
"com.android.tethering",
+ "com.android.wifi",
+ "//apex_available:platform",
],
},
cpp: {
@@ -592,8 +596,7 @@
],
}
-// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
-// rules on the wifi side.
+// Filegroup to build lib used by Wifi framework
// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
filegroup {
name: "net-utils-framework-wifi-common-srcs",
@@ -605,26 +608,7 @@
"framework/com/android/net/module/util/NetUtils.java",
],
path: "framework",
- visibility: [
- "//frameworks/base",
- ],
-}
-
-// Use a filegroup and not a library for wifi sources, as this needs corresponding jar-jar
-// rules on the wifi side.
-// Any class here *must* have a corresponding jarjar rule in the wifi build rules.
-filegroup {
- name: "net-utils-wifi-service-common-srcs",
- srcs: [
- "device/android/net/NetworkFactory.java",
- "device/android/net/NetworkFactoryImpl.java",
- "device/android/net/NetworkFactoryLegacyImpl.java",
- "device/android/net/NetworkFactoryShim.java",
- ],
- visibility: [
- "//frameworks/opt/net/wifi/service",
- "//packages/modules/Wifi/service",
- ],
+ visibility: ["//visibility:private"],
}
// Use a file group containing classes necessary for framework-connectivity. The file group should
@@ -664,3 +648,38 @@
path: "device",
visibility: ["//visibility:private"],
}
+
+java_library {
+ name: "net-utils-service-wifi",
+ srcs: [
+ ":net-utils-all-srcs",
+ ],
+ exclude_srcs: [":net-utils-framework-wifi-common-srcs"],
+ libs: [
+ "net-utils-framework-wifi",
+ ],
+ defaults: ["net-utils-non-bootclasspath-defaults"],
+
+ visibility: [
+ "//packages/modules/Wifi/service",
+ ],
+ apex_available: [
+ "com.android.wifi",
+ ],
+}
+
+java_library {
+ name: "net-utils-framework-wifi",
+ sdk_version: "module_current",
+ min_sdk_version: "30",
+ srcs: [":net-utils-framework-wifi-common-srcs"],
+ libs: [
+ "framework-annotations-lib",
+ "framework-connectivity.stubs.module_lib",
+ "unsupportedappusage",
+ ],
+ visibility: [
+ "//packages/modules/Wifi/framework",
+ ],
+ apex_available: ["com.android.wifi"],
+}
diff --git a/staticlibs/device/com/android/net/module/util/GrowingIntArray.java b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
index 4a81c10..d47738b 100644
--- a/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
+++ b/staticlibs/device/com/android/net/module/util/GrowingIntArray.java
@@ -168,7 +168,7 @@
* stop using this instance of {@link GrowingIntArray} if they use the array returned by this
* method.
*/
- public int[] getShrinkedBackingArray() {
+ public int[] getMinimizedBackingArray() {
shrinkToLength();
return mValues;
}
diff --git a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
index 463b0c4..98d91a5 100644
--- a/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
+++ b/staticlibs/framework/com/android/net/module/util/PerUidCounter.java
@@ -87,7 +87,9 @@
}
}
- @VisibleForTesting
+ /**
+ * Get the current counter value for the given uid.
+ */
public synchronized int get(int uid) {
return mUidToCount.get(uid, 0);
}
diff --git a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
index cd51004..4bcd259 100644
--- a/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
+++ b/staticlibs/native/bpf_headers/include/bpf/BpfRingbuf.h
@@ -20,6 +20,7 @@
#include <android-base/unique_fd.h>
#include <linux/bpf.h>
#include <poll.h>
+#include <sys/epoll.h>
#include <sys/mman.h>
#include <utils/Log.h>
@@ -33,7 +34,7 @@
// BpfRingbufBase contains the non-templated functionality of BPF ring buffers.
class BpfRingbufBase {
public:
- ~BpfRingbufBase() {
+ virtual ~BpfRingbufBase() {
if (mConsumerPos) munmap(mConsumerPos, mConsumerSize);
if (mProducerPos) munmap(mProducerPos, mProducerSize);
mConsumerPos = nullptr;
@@ -139,12 +140,24 @@
static base::Result<std::unique_ptr<BpfRingbuf<Value>>> Create(
const char* path);
+ int epoll_ctl_add(int epfd, struct epoll_event *event) {
+ return epoll_ctl(epfd, EPOLL_CTL_ADD, mRingFd.get(), event);
+ }
+
+ int epoll_ctl_mod(int epfd, struct epoll_event *event) {
+ return epoll_ctl(epfd, EPOLL_CTL_MOD, mRingFd.get(), event);
+ }
+
+ int epoll_ctl_del(int epfd) {
+ return epoll_ctl(epfd, EPOLL_CTL_DEL, mRingFd.get(), NULL);
+ }
+
// Consumes all messages from the ring buffer, passing them to the callback.
// Returns the number of messages consumed or a non-ok result on error. If the
// ring buffer has no pending messages an OK result with count 0 is returned.
base::Result<int> ConsumeAll(const MessageCallback& callback);
- private:
+ protected:
// Empty ctor for use by Create.
BpfRingbuf() : BpfRingbufBase(sizeof(Value)) {}
};
diff --git a/staticlibs/tests/unit/host/python/apf_utils_test.py b/staticlibs/tests/unit/host/python/apf_utils_test.py
index caaf959..b5a941b 100644
--- a/staticlibs/tests/unit/host/python/apf_utils_test.py
+++ b/staticlibs/tests/unit/host/python/apf_utils_test.py
@@ -26,11 +26,14 @@
get_apf_counter,
get_apf_counters_from_dumpsys,
get_hardware_address,
- send_broadcast_empty_ethercat_packet,
+ is_send_raw_packet_downstream_supported,
send_raw_packet_downstream,
)
from net_tests_utils.host.python.assert_utils import UnexpectedBehaviorError
+TEST_IFACE_NAME = "eth0"
+TEST_PACKET_IN_HEX = "AABBCCDDEEFF"
+
class TestApfUtils(base_test.BaseTestClass, parameterized.TestCase):
@@ -108,30 +111,18 @@
with asserts.assert_raises(PatternNotFoundException):
get_hardware_address(self.mock_ad, "wlan0")
- @patch("net_tests_utils.host.python.apf_utils.get_hardware_address")
- @patch("net_tests_utils.host.python.apf_utils.send_raw_packet_downstream")
- def test_send_broadcast_empty_ethercat_packet(
- self,
- mock_send_raw_packet_downstream: MagicMock,
- mock_get_hardware_address: MagicMock,
- ) -> None:
- mock_get_hardware_address.return_value = "12:34:56:78:90:AB"
- send_broadcast_empty_ethercat_packet(self.mock_ad, "eth0")
- # Assuming you'll mock the packet construction part, verify calls to send_raw_packet_downstream.
- mock_send_raw_packet_downstream.assert_called_once()
-
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
def test_send_raw_packet_downstream_success(
self, mock_adb_shell: MagicMock
) -> None:
mock_adb_shell.return_value = "" # Successful command output
- iface_name = "eth0"
- packet_in_hex = "AABBCCDDEEFF"
- send_raw_packet_downstream(self.mock_ad, iface_name, packet_in_hex)
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
mock_adb_shell.assert_called_once_with(
self.mock_ad,
"cmd network_stack send-raw-packet-downstream"
- f" {iface_name} {packet_in_hex}",
+ f" {TEST_IFACE_NAME} {TEST_PACKET_IN_HEX}",
)
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
@@ -142,7 +133,13 @@
"Any Unexpected Output"
)
with asserts.assert_raises(UnexpectedBehaviorError):
- send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_true(
+ is_send_raw_packet_downstream_supported(self.mock_ad),
+ "Send raw packet should be supported.",
+ )
@patch("net_tests_utils.host.python.adb_utils.adb_shell")
def test_send_raw_packet_downstream_unsupported(
@@ -152,7 +149,13 @@
cmd="", stdout="Unknown command", stderr="", ret_code=3
)
with asserts.assert_raises(UnsupportedOperationException):
- send_raw_packet_downstream(self.mock_ad, "eth0", "AABBCCDDEEFF")
+ send_raw_packet_downstream(
+ self.mock_ad, TEST_IFACE_NAME, TEST_PACKET_IN_HEX
+ )
+ asserts.assert_false(
+ is_send_raw_packet_downstream_supported(self.mock_ad),
+ "Send raw packet should not be supported.",
+ )
@parameterized.parameters(
("2,2048,1", ApfCapabilities(2, 2048, 1)), # Valid input
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
index bdcb8c0..4b740e3 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/GrowingIntArrayTest.kt
@@ -109,12 +109,12 @@
}
@Test
- fun testGetShrinkedBackingArray() {
+ fun testGetMinimizedBackingArray() {
val array = GrowingIntArray(10)
array.add(-1)
array.add(2)
- assertContentEquals(intArrayOf(-1, 2), array.shrinkedBackingArray)
+ assertContentEquals(intArrayOf(-1, 2), array.minimizedBackingArray)
}
@Test
diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp
index 4749e75..8c71a91 100644
--- a/staticlibs/testutils/Android.bp
+++ b/staticlibs/testutils/Android.bp
@@ -42,7 +42,6 @@
"net-utils-device-common-struct",
"net-utils-device-common-struct-base",
"net-utils-device-common-wear",
- "net-utils-framework-connectivity",
"modules-utils-build_system",
],
lint: {
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt b/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt
new file mode 100644
index 0000000..89de0b3
--- /dev/null
+++ b/staticlibs/testutils/devicetests/com/android/testutils/AutoCloseTestInterfaceRule.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.testutils
+
+import android.Manifest.permission.MANAGE_TEST_NETWORKS
+import android.content.Context
+import android.net.TestNetworkInterface
+import android.net.TestNetworkManager
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class AutoCloseTestInterfaceRule(
+ private val context: Context,
+ ) : TestRule {
+ private val tnm = runAsShell(MANAGE_TEST_NETWORKS) {
+ context.getSystemService(TestNetworkManager::class.java)!!
+ }
+ private val ifaces = ArrayList<TestNetworkInterface>()
+
+ fun createTapInterface(): TestNetworkInterface {
+ return runAsShell(MANAGE_TEST_NETWORKS) {
+ tnm.createTapInterface()
+ }.also {
+ ifaces.add(it)
+ }
+ }
+
+ private fun closeAllInterfaces() {
+ // TODO: wait on RTM_DELLINK before proceeding.
+ for (iface in ifaces) {
+ // ParcelFileDescriptor prevents the fd from being double closed.
+ iface.getFileDescriptor().close()
+ }
+ }
+
+ private inner class AutoCloseTestInterfaceRuleStatement(
+ private val base: Statement,
+ private val description: Description
+ ) : Statement() {
+ override fun evaluate() {
+ tryTest {
+ base.evaluate()
+ } cleanup {
+ closeAllInterfaces()
+ }
+ }
+ }
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return AutoCloseTestInterfaceRuleStatement(base, description)
+ }
+}
diff --git a/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
index 1b709b2..dd52d0b 100644
--- a/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
+++ b/staticlibs/testutils/devicetests/com/android/testutils/DefaultNetworkRestoreMonitor.kt
@@ -34,7 +34,7 @@
class DefaultNetworkRestoreMonitor(
ctx: Context,
private val notifier: RunNotifier,
- private val timeoutMs: Long = 3000
+ private val timeoutMs: Long = 30_000
) {
var firstFailure: Exception? = null
var initialTransports = 0L
@@ -56,8 +56,8 @@
it.caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
}
} catch (e: AssertionError) {
- firstFailure = IllegalStateException(desc.methodName +
- " does not restore the default network")
+ firstFailure = IllegalStateException(desc.methodName + " does not restore the" +
+ "default network, initialTransports = $initialTransports", e)
} finally {
cm.unregisterNetworkCallback(cb)
}
@@ -88,7 +88,7 @@
}
cm.registerDefaultNetworkCallback(cb)
try {
- val cap = capFuture.get(100, TimeUnit.MILLISECONDS)
+ val cap = capFuture.get(10_000, TimeUnit.MILLISECONDS)
initialTransports = BitUtils.packBits(cap.transportTypes)
} catch (e: Exception) {
firstFailure = IllegalStateException(
diff --git a/staticlibs/testutils/host/python/apf_test_base.py b/staticlibs/testutils/host/python/apf_test_base.py
new file mode 100644
index 0000000..7203265
--- /dev/null
+++ b/staticlibs/testutils/host/python/apf_test_base.py
@@ -0,0 +1,80 @@
+# 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.
+
+from mobly import asserts
+from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, multi_devices_test_base, tether_utils
+from net_tests_utils.host.python.tether_utils import UpstreamType
+
+
+class ApfTestBase(multi_devices_test_base.MultiDevicesTestBase):
+
+ def setup_class(self):
+ super().setup_class()
+
+ # Check test preconditions.
+ tether_utils.assume_hotspot_test_preconditions(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ asserts.abort_class_if(
+ not apf_utils.is_send_raw_packet_downstream_supported(
+ self.serverDevice
+ ),
+ "NetworkStack is too old to support send raw packet, skip test.",
+ )
+
+ # Fetch device properties and storing them locally for later use.
+ client = self.clientDevice.connectivity_multi_devices_snippet
+ self.server_iface_name, client_network = (
+ tether_utils.setup_hotspot_and_client_for_upstream_type(
+ self.serverDevice, self.clientDevice, UpstreamType.NONE
+ )
+ )
+ self.client_iface_name = client.getInterfaceNameFromNetworkHandle(
+ client_network
+ )
+ self.server_mac_address = apf_utils.get_hardware_address(
+ self.serverDevice, self.server_iface_name
+ )
+
+ # Enable doze mode to activate APF.
+ adb_utils.set_doze_mode(self.clientDevice, True)
+
+ def teardown_class(self):
+ adb_utils.set_doze_mode(self.clientDevice, False)
+ tether_utils.cleanup_tethering_for_upstream_type(
+ self.serverDevice, UpstreamType.NONE
+ )
+
+ def send_packet_and_expect_counter_increased(
+ self, packet: str, counter_name: str
+ ) -> None:
+ count_before_test = apf_utils.get_apf_counter(
+ self.clientDevice,
+ self.client_iface_name,
+ counter_name,
+ )
+ apf_utils.send_raw_packet_downstream(
+ self.serverDevice, self.server_iface_name, packet
+ )
+
+ assert_utils.expect_with_retry(
+ lambda: apf_utils.get_apf_counter(
+ self.clientDevice,
+ self.client_iface_name,
+ counter_name,
+ )
+ > count_before_test
+ )
+
+ # TODO: Verify the packet is not actually received.
diff --git a/staticlibs/testutils/host/python/apf_utils.py b/staticlibs/testutils/host/python/apf_utils.py
index 415799c..a3ec6e9 100644
--- a/staticlibs/testutils/host/python/apf_utils.py
+++ b/staticlibs/testutils/host/python/apf_utils.py
@@ -20,11 +20,6 @@
from net_tests_utils.host.python import adb_utils, assert_utils
-# Constants.
-ETHER_BROADCAST = "FFFFFFFFFFFF"
-ETH_P_ETHERCAT = "88A4"
-
-
class PatternNotFoundException(Exception):
"""Raised when the given pattern cannot be found."""
@@ -121,25 +116,17 @@
)
-def send_broadcast_empty_ethercat_packet(
- ad: android_device.AndroidDevice, iface_name: str
-) -> None:
- """Transmits a broadcast empty EtherCat packet on the specified interface."""
-
- # Get the interface's MAC address.
- mac_address = get_hardware_address(ad, iface_name)
-
- # TODO: Build packet by using scapy library.
- # Ethernet header (14 bytes).
- packet = ETHER_BROADCAST # Destination MAC (broadcast)
- packet += mac_address.replace(":", "") # Source MAC
- packet += ETH_P_ETHERCAT # EtherType (EtherCAT)
-
- # EtherCAT header (2 bytes) + 44 bytes of zero padding.
- packet += "00" * 46
-
- # Send the packet using a raw socket.
- send_raw_packet_downstream(ad, iface_name, packet)
+def is_send_raw_packet_downstream_supported(
+ ad: android_device.AndroidDevice,
+) -> bool:
+ try:
+ # Invoke the shell command with empty argument and see how NetworkStack respond.
+ # If supported, an IllegalArgumentException with help page will be printed.
+ send_raw_packet_downstream(ad, "", "")
+ except assert_utils.UnexpectedBehaviorError:
+ return True
+ except UnsupportedOperationException:
+ return False
def send_raw_packet_downstream(
diff --git a/staticlibs/testutils/host/python/multi_devices_test_base.py b/staticlibs/testutils/host/python/multi_devices_test_base.py
new file mode 100644
index 0000000..f8a92f3
--- /dev/null
+++ b/staticlibs/testutils/host/python/multi_devices_test_base.py
@@ -0,0 +1,54 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# 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.
+
+from mobly import base_test
+from mobly import utils
+from mobly.controllers import android_device
+
+CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
+
+
+class MultiDevicesTestBase(base_test.BaseTestClass):
+
+ def setup_class(self):
+ # Declare that two Android devices are needed.
+ self.clientDevice, self.serverDevice = self.register_controller(
+ android_device, min_number=2
+ )
+
+ def setup_device(device):
+ device.load_snippet(
+ "connectivity_multi_devices_snippet",
+ CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
+ )
+
+ # Set up devices in parallel to save time.
+ utils.concurrent_exec(
+ setup_device,
+ ((self.clientDevice,), (self.serverDevice,)),
+ max_workers=2,
+ raise_on_exception=True,
+ )
diff --git a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
index 8cef6aa..17f5e96 100644
--- a/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
+++ b/tests/common/java/android/net/netstats/NetworkStatsApiTest.kt
@@ -28,18 +28,26 @@
import android.net.NetworkStats.SET_DEFAULT
import android.net.NetworkStats.SET_FOREGROUND
import android.net.NetworkStats.TAG_NONE
+import android.os.Build
import androidx.test.filters.SmallTest
+import com.android.testutils.ConnectivityModuleTest
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.assertNetworkStatsEquals
import com.android.testutils.assertParcelingIsLossless
import kotlin.test.assertEquals
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-@RunWith(JUnit4::class)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
class NetworkStatsApiTest {
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
private val testStatsEmpty = NetworkStats(0L, 0)
// Note that these variables need to be initialized outside of constructor, initialize
@@ -49,6 +57,7 @@
// be merged if performing add on these 2 stats.
private lateinit var testStats1: NetworkStats
private lateinit var testStats2: NetworkStats
+ private lateinit var expectedEntriesInStats2: List<Entry>
// This is a result of adding stats1 and stats2, while the merging of common key items is
// subject to test later, this should not be initialized with for a loop to add stats1
@@ -84,19 +93,23 @@
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 1, 6, 2, 0))
assertEquals(8, testStats1.size())
- testStats2 = NetworkStats(0L, 0)
- // Entries which are common for set1 and set2.
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1))
- .addEntry(Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45))
- .addEntry(Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7))
- .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0))
- // Entry which only appears in set2.
- .addEntry(Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ expectedEntriesInStats2 = listOf(
+ // Entries which are common for set1 and set2.
+ Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1),
+ Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45),
+ Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7),
+ Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0),
+ // Entry which only appears in set2.
+ Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
+ testStats2 = NetworkStats(0L, 5)
+ for (entry in expectedEntriesInStats2) {
+ testStats2 = testStats2.addEntry(entry)
+ }
assertEquals(5, testStats2.size())
testStats3 = NetworkStats(0L, 9)
@@ -125,18 +138,6 @@
@Test
fun testAddEntry() {
- val expectedEntriesInStats2 = arrayOf(
- Entry(TEST_IFACE, TEST_UID1, SET_DEFAULT, 0x80,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 3, 15, 2, 31, 1),
- Entry(TEST_IFACE, TEST_UID1, SET_FOREGROUND, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 13, 61, 10, 1, 45),
- Entry(TEST_IFACE, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 11, 2, 3, 4, 7),
- Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 4, 3, 2, 1, 0),
- Entry(IFACE_VT, TEST_UID2, SET_DEFAULT, TAG_NONE,
- METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 2, 3, 7, 8, 0))
-
// While testStats* are already initialized with addEntry, verify content added
// matches expectation.
for (i in expectedEntriesInStats2.indices) {
@@ -150,6 +151,27 @@
assertEquals(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 16, -2, 9, 1, 9),
stats.getValues(3, null))
+
+ // Verify the original ststs object is not altered.
+ for (i in expectedEntriesInStats2.indices) {
+ val entry = testStats2.getValues(i, null)
+ assertEquals(expectedEntriesInStats2[i], entry)
+ }
+ }
+
+ @ConnectivityModuleTest
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2) // Mainlined NetworkStats only runs on T+
+ @Test
+ fun testAddEntries() {
+ val baseStats = NetworkStats(0L, 1)
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9))
+ val statsUnderTest = baseStats.addEntries(expectedEntriesInStats2)
+ // Assume the correctness of addEntry is verified in other tests.
+ val expectedStats = testStats2
+ .addEntry(Entry(IFACE_VT, TEST_UID1, SET_DEFAULT, TAG_NONE,
+ METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 12, -5, 7, 0, 9))
+ assertNetworkStatsEquals(expectedStats, statsUnderTest)
}
@Test
diff --git a/tests/cts/hostside-network-policy/Android.bp b/tests/cts/hostside-network-policy/Android.bp
deleted file mode 100644
index c3ce0b9..0000000
--- a/tests/cts/hostside-network-policy/Android.bp
+++ /dev/null
@@ -1,46 +0,0 @@
-// 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_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_host {
- name: "CtsHostsideNetworkPolicyTests",
- defaults: ["cts_defaults"],
- // Only compile source java files in this apk.
- srcs: [
- "src/**/*.java",
- ":ArgumentConstants",
- ],
- libs: [
- "cts-tradefed",
- "tradefed",
- ],
- static_libs: [
- "modules-utils-build-testing",
- ],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- data: [
- ":CtsHostsideNetworkPolicyTestsApp",
- ":CtsHostsideNetworkPolicyTestsApp2",
- ],
- per_testcase_directory: true,
-}
diff --git a/tests/cts/hostside-network-policy/AndroidTest.xml b/tests/cts/hostside-network-policy/AndroidTest.xml
deleted file mode 100644
index 44f77f8..0000000
--- a/tests/cts/hostside-network-policy/AndroidTest.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?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="Config for CTS network policy host test cases">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="networking" />
- <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
- <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
- <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
- <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
- <target_preparer class="com.android.cts.netpolicy.NetworkPolicyTestsPreparer" />
-
- <!-- Enabling change id ALLOW_TEST_API_ACCESS allows that package to access @TestApi methods -->
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command" value="am compat enable ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
- <option name="teardown-command" value="am compat reset ALLOW_TEST_API_ACCESS com.android.cts.netpolicy.hostside.app2" />
- <option name="teardown-command" value="cmd power set-mode 0" />
- <option name="teardown-command" value="cmd battery reset" />
- <option name="teardown-command" value="cmd netpolicy stop-watching" />
- </target_preparer>
-
- <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
- <option name="force-skip-system-props" value="true" />
- <option name="set-global-setting" key="verifier_verify_adb_installs" value="0" />
- <option name="set-global-setting" key="low_power_standby_enabled" value="0" />
- </target_preparer>
-
- <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
- <option name="jar" value="CtsHostsideNetworkPolicyTests.jar" />
- <option name="runtime-hint" value="3m56s" />
- </test>
-
- <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
- <option name="directory-keys" value="/sdcard/CtsHostsideNetworkPolicyTests" />
- <option name="collect-on-run-ended-only" value="true" />
- </metrics_collector>
-</configuration>
diff --git a/tests/cts/hostside-network-policy/OWNERS b/tests/cts/hostside-network-policy/OWNERS
deleted file mode 100644
index ea83e61..0000000
--- a/tests/cts/hostside-network-policy/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 61373
-# Inherits parent owners
-include platform/frameworks/base:/services/core/java/com/android/server/net/OWNERS
diff --git a/tests/cts/hostside-network-policy/TEST_MAPPING b/tests/cts/hostside-network-policy/TEST_MAPPING
deleted file mode 100644
index 57ac4f7..0000000
--- a/tests/cts/hostside-network-policy/TEST_MAPPING
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "presubmit-large": [
- {
- "name": "CtsHostsideNetworkPolicyTests",
- "options": [
- {
- "exclude-annotation": "android.platform.test.annotations.FlakyTest"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
- }
- ]
- }
- ],
- "postsubmit": [
- {
- // Postsubmit on virtual devices to monitor flakiness of all tests that don't require a
- // physical device
- "name": "CtsHostsideNetworkPolicyTests",
- "options": [
- {
- "exclude-annotation": "androidx.test.filters.RequiresDevice"
- }
- ]
- }
- ]
-}
diff --git a/tests/cts/hostside-network-policy/aidl/Android.bp b/tests/cts/hostside-network-policy/aidl/Android.bp
deleted file mode 100644
index b182090..0000000
--- a/tests/cts/hostside-network-policy/aidl/Android.bp
+++ /dev/null
@@ -1,26 +0,0 @@
-// 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_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_helper_library {
- name: "CtsHostsideNetworkPolicyTestsAidl",
- sdk_version: "current",
- srcs: [
- "com/android/cts/netpolicy/hostside/*.aidl",
- ],
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
deleted file mode 100644
index 068d9d8..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/IMyService.aidl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.job.JobInfo;
-
-import com.android.cts.netpolicy.hostside.INetworkCallback;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-interface IMyService {
- void registerBroadcastReceiver();
- int getCounters(String receiverName, String action);
- NetworkCheckResult checkNetworkStatus(String customUrl);
- String getRestrictBackgroundStatus();
- void sendNotification(int notificationId, String notificationType);
- void registerNetworkCallback(in NetworkRequest request, in INetworkCallback cb);
- void unregisterNetworkCallback();
- int scheduleJob(in JobInfo jobInfo);
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
deleted file mode 100644
index 38efc7b..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkCallback.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import android.net.Network;
-import android.net.NetworkCapabilities;
-
-interface INetworkCallback {
- void onBlockedStatusChanged(in Network network, boolean blocked);
- void onAvailable(in Network network);
- void onLost(in Network network);
- void onCapabilitiesChanged(in Network network, in NetworkCapabilities cap);
-}
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
deleted file mode 100644
index c6b7a1c..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/INetworkStateObserver.aidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.net.NetworkInfo;
-
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-interface INetworkStateObserver {
- void onNetworkStateChecked(int resultCode, in NetworkCheckResult networkCheckResult);
-
- const int RESULT_SUCCESS_NETWORK_STATE_CHECKED = 0;
- const int RESULT_ERROR_UNEXPECTED_PROC_STATE = 1;
- const int RESULT_ERROR_UNEXPECTED_CAPABILITIES = 2;
- const int RESULT_ERROR_OTHER = 3;
-}
\ No newline at end of file
diff --git a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl b/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
deleted file mode 100644
index 7aac2ab..0000000
--- a/tests/cts/hostside-network-policy/aidl/com/android/cts/netpolicy/hostside/NetworkCheckResult.aidl
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.cts.netpolicy.hostside;
-
-import android.net.NetworkInfo;
-
-@JavaDerive(toString=true)
-parcelable NetworkCheckResult {
- boolean connected;
- String details;
- NetworkInfo networkInfo;
-}
\ No newline at end of file
diff --git a/tests/cts/hostside-network-policy/app/Android.bp b/tests/cts/hostside-network-policy/app/Android.bp
deleted file mode 100644
index a31c843..0000000
--- a/tests/cts/hostside-network-policy/app/Android.bp
+++ /dev/null
@@ -1,57 +0,0 @@
-//
-// 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_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_defaults {
- name: "CtsHostsideNetworkPolicyTestsAppDefaults",
- platform_apis: true,
- static_libs: [
- "CtsHostsideNetworkPolicyTestsAidl",
- "androidx.test.ext.junit",
- "androidx.test.rules",
- "androidx.test.uiautomator_uiautomator",
- "compatibility-device-util-axt",
- "cts-net-utils",
- "ctstestrunner-axt",
- "modules-utils-build",
- ],
- libs: [
- "android.test.runner",
- "android.test.base",
- ],
- srcs: [
- "src/**/*.java",
- ":ArgumentConstants",
- ],
- // Tag this module as a cts test artifact
- test_suites: [
- "general-tests",
- "sts",
- ],
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkPolicyTestsApp",
- defaults: [
- "cts_support_defaults",
- "framework-connectivity-test-defaults",
- "CtsHostsideNetworkPolicyTestsAppDefaults",
- ],
-}
diff --git a/tests/cts/hostside-network-policy/app/AndroidManifest.xml b/tests/cts/hostside-network-policy/app/AndroidManifest.xml
deleted file mode 100644
index f19e35f..0000000
--- a/tests/cts/hostside-network-policy/app/AndroidManifest.xml
+++ /dev/null
@@ -1,50 +0,0 @@
-<?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="com.android.cts.netpolicy.hostside">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
- <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
- <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
- <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
- <uses-permission android:name="android.permission.WAKE_LOCK" />
-
- <application android:requestLegacyExternalStorage="true">
- <uses-library android:name="android.test.runner"/>
- <service android:name=".MyNotificationListenerService"
- android:label="MyNotificationListenerService"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"
- android:exported="true">
- <intent-filter>
- <action android:name="android.service.notification.NotificationListenerService"/>
- </intent-filter>
- </service>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.netpolicy.hostside"/>
-
-</manifest>
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
deleted file mode 100644
index 19e4364..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractAppIdleTestCase.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-
-import static org.junit.Assert.assertEquals;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered tests on idle apps.
- */
-@RequiredProperties({APP_STANDBY_MODE})
-abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setAppIdle(false);
- turnBatteryOn();
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- resetBatteryState();
- setAppIdle(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground app doesn't lose access upon enabling it.
- setAppIdle(true);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- finishActivity();
- assertAppIdle(false); // verify - not idle anymore, since activity was launched...
- assertBackgroundNetworkAccess(true);
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Same for foreground service.
- setAppIdle(true);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- // Set Idle after foreground service start.
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setAppIdle(true);
- addPowerSaveModeWhitelist(TEST_PKG);
- removePowerSaveModeWhitelist(TEST_PKG);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // verify - not idle anymore, since whitelisted
- assertBackgroundNetworkAccess(true);
-
- setAppIdleNoAssert(true);
- assertAppIdle(false); // app is still whitelisted
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // verify - idle again, once whitelisted was removed
- assertBackgroundNetworkAccess(false);
-
- setAppIdle(true);
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // verify - not idle anymore, since whitelisted
- assertBackgroundNetworkAccess(true);
-
- setAppIdleNoAssert(true);
- assertAppIdle(false); // app is still whitelisted
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // verify - idle again, once whitelisted was removed
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
-
- // verify - no whitelist, no access!
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleNetworkAccess_whenCharging() throws Exception {
- // Check that app is paroled when charging
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- turnBatteryOff();
- assertBackgroundNetworkAccess(true);
- turnBatteryOn();
- assertBackgroundNetworkAccess(false);
-
- // Check that app is restricted when not idle but power-save is on
- setAppIdle(false);
- assertBackgroundNetworkAccess(true);
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- // Use setBatterySaverMode API to leave power-save mode instead of plugging in charger
- setBatterySaverMode(false);
- turnBatteryOff();
- assertBackgroundNetworkAccess(true);
-
- // And when no longer charging, it still has network access, since it's not idle
- turnBatteryOn();
- assertBackgroundNetworkAccess(true);
- }
-
- @Test
- public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
- setAppIdle(true);
- assertAppIdle(true);
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
-
- removeAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- // Make sure whitelisting a random app doesn't affect the tested app.
- addAppIdleWhitelist(mUid + 1);
- assertBackgroundNetworkAccess(false);
- removeAppIdleWhitelist(mUid + 1);
- }
-
- @Test
- public void testAppIdle_toast() throws Exception {
- setAppIdle(true);
- assertAppIdle(true);
- assertEquals("Shown", showToast());
- assertAppIdle(true);
- // Wait for a couple of seconds for the toast to actually be shown
- SystemClock.sleep(2000);
- assertAppIdle(true);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
deleted file mode 100644
index ae226e2..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractBatterySaverModeTestCase.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered Battery Saver Mode tests.
- */
-@RequiredProperties({BATTERY_SAVER_MODE})
-abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setBatterySaverMode(false);
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- setBatterySaverMode(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground app doesn't lose access upon Battery Saver.
- setBatterySaverMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setBatterySaverMode(true);
- assertTopNetworkAccess(true);
-
- // Although it should not have access while the screen is off.
- turnScreenOff();
- assertBackgroundNetworkAccess(false);
- turnScreenOn();
- assertTopNetworkAccess(true);
-
- // Goes back to background state.
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose access upon enabling Battery Saver.
- setBatterySaverMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setBatterySaverMode(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
deleted file mode 100644
index 00f67f4..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDefaultRestrictionsTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for default, always-on network restrictions.
- */
-abstract class AbstractDefaultRestrictionsTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
-
- registerBroadcastReceiver();
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- stopApp();
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- }
-
- @Test
- public void testFgsNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- }
-
- @Test
- public void testActivityNetworkAccess() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- }
-
- @Test
- public void testBackgroundNetworkAccess_inFullAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- assertNetworkAccess(true, null);
- }
-
- @Test
- public void testBackgroundNetworkAccess_inExceptIdleAllowlist() throws Exception {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkAccess(false, null);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- assertNetworkAccess(true, null);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
deleted file mode 100644
index 0c8cb70..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractDozeModeTestCase.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.NOT_LOW_RAM_DEVICE;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Base class for metered and non-metered Doze Mode tests.
- */
-@RequiredProperties({DOZE_MODE})
-abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- setDozeMode(false);
-
- registerBroadcastReceiver();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
-
- setDozeMode(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_enabled() throws Exception {
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose network access upon enabling doze.
- setDozeMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setDozeMode(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_whitelisted() throws Exception {
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testBackgroundNetworkAccess_disabled() throws Exception {
- assertBackgroundNetworkAccess(true);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- }
-
- @RequiredProperties({NOT_LOW_RAM_DEVICE})
- @Test
- public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
- throws Exception {
- setPendingIntentAllowlistDuration(NETWORK_TIMEOUT_MS);
- try {
- registerNotificationListenerService();
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
-
- testNotification(4, NOTIFICATION_TYPE_CONTENT);
- testNotification(8, NOTIFICATION_TYPE_DELETE);
- testNotification(15, NOTIFICATION_TYPE_FULL_SCREEN);
- testNotification(16, NOTIFICATION_TYPE_BUNDLE);
- testNotification(23, NOTIFICATION_TYPE_ACTION);
- testNotification(42, NOTIFICATION_TYPE_ACTION_BUNDLE);
- testNotification(108, NOTIFICATION_TYPE_ACTION_REMOTE_INPUT);
- } finally {
- resetDeviceIdleSettings();
- }
- }
-
- private void testNotification(int id, String type) throws Exception {
- sendNotification(id, type);
- assertBackgroundNetworkAccess(true);
- if (type.equals(NOTIFICATION_TYPE_ACTION)) {
- // Make sure access is disabled after it expires. Since this check considerably slows
- // downs the CTS tests, do it just once.
- SystemClock.sleep(NETWORK_TIMEOUT_MS);
- assertBackgroundNetworkAccess(false);
- }
- }
-
- // Must override so it only tests foreground service - once an app goes to foreground, device
- // leaves Doze Mode.
- @Override
- protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
deleted file mode 100644
index 5435920..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractExpeditedJobTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class AbstractExpeditedJobTest extends AbstractRestrictBackgroundNetworkTestCase {
- @Before
- public final void setUp() throws Exception {
- super.setUp();
- resetDeviceState();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
- resetDeviceState();
- }
-
- private void resetDeviceState() throws Exception {
- resetBatteryState();
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
- setDozeMode(false);
- }
-
- @Test
- @RequiredProperties({BATTERY_SAVER_MODE})
- public void testNetworkAccess_batterySaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dataSaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({APP_STANDBY_MODE})
- public void testNetworkAccess_appIdleState() throws Exception {
- turnBatteryOn();
- setAppIdle(false);
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setAppIdle(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DOZE_MODE})
- public void testNetworkAccess_dozeMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dataAndBatterySaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setBatterySaverMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DOZE_MODE, DATA_SAVER_MODE, METERED_NETWORK})
- public void testNetworkAccess_dozeAndDataSaverMode() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK, DOZE_MODE,
- APP_STANDBY_MODE})
- public void testNetworkAccess_allRestrictionsEnabled() throws Exception {
- assertBackgroundNetworkAccess(true);
- assertExpeditedJobHasNetworkAccess();
-
- setRestrictBackground(true);
- setBatterySaverMode(true);
- setAppIdle(true);
- setDozeMode(true);
- assertBackgroundNetworkAccess(false);
- assertExpeditedJobHasNoNetworkAccess();
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
deleted file mode 100644
index 0f5f58c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ /dev/null
@@ -1,1165 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP;
-import static android.app.job.JobScheduler.RESULT_SUCCESS;
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static android.os.BatteryManager.BATTERY_PLUGGED_ANY;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.executeShellCommand;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.forceRunJob;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getConnectivityManager;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getContext;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackgroundInternal;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.annotation.NonNull;
-import android.app.Instrumentation;
-import android.app.NotificationManager;
-import android.app.job.JobInfo;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkInfo.State;
-import android.net.NetworkRequest;
-import android.os.BatteryManager;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.PowerManager;
-import android.os.RemoteCallback;
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.service.notification.NotificationListenerService;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.AmUtils;
-import com.android.compatibility.common.util.BatteryUtils;
-import com.android.compatibility.common.util.DeviceConfigStateHelper;
-import com.android.compatibility.common.util.ThrowingRunnable;
-import com.android.modules.utils.build.SdkLevel;
-
-import org.junit.Rule;
-import org.junit.rules.RuleChain;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Predicate;
-
-/**
- * Superclass for tests related to background network restrictions.
- */
-@RunWith(NetworkPolicyTestRunner.class)
-public abstract class AbstractRestrictBackgroundNetworkTestCase {
- public static final String TAG = "RestrictBackgroundNetworkTests";
-
- protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
- // TODO(b/321797685): Configure it via device-config once it is available.
- protected final long mProcessStateTransitionLongDelayMs =
- useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(20)
- : TimeUnit.SECONDS.toMillis(5);
- protected final long mProcessStateTransitionShortDelayMs =
- useDifferentDelaysForBackgroundChain() ? TimeUnit.SECONDS.toMillis(2)
- : TimeUnit.SECONDS.toMillis(5);
-
- private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
- private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
- private static final String TEST_APP2_JOB_SERVICE_CLASS = TEST_APP2_PKG + ".MyJobService";
-
- private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
- TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
- private static final int TEST_JOB_ID = 7357437;
-
- private static final int SLEEP_TIME_SEC = 1;
-
- // Constants below must match values defined on app2's Common.java
- private static final String MANIFEST_RECEIVER = "ManifestReceiver";
- private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
- private static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
- private static final String ACTION_FINISH_JOB =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
- // Copied from com.android.server.net.NetworkPolicyManagerService class
- private static final String ACTION_SNOOZE_WARNING =
- "com.android.server.net.action.SNOOZE_WARNING";
-
- private static final String ACTION_RECEIVER_READY =
- "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
- static final String ACTION_SHOW_TOAST =
- "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
-
- protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
- protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
- protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
- protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
- protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
- protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
- protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
-
- private static final String NETWORK_STATUS_SEPARATOR = "\\|";
- private static final int SECOND_IN_MS = 1000;
- static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
-
- private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
- private static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
- private static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
-
- private static final String EMPTY_STRING = "";
-
- protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
- protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
- protected static final int TYPE_EXPEDITED_JOB = 2;
-
- private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
- private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
-
- private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 10_000;
- private static final int JOB_NETWORK_STATE_TIMEOUT_MS = 10_000;
- private static final int LAUNCH_ACTIVITY_TIMEOUT_MS = 10_000;
-
- // Must be higher than NETWORK_TIMEOUT_MS
- private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
-
- private static final IntentFilter BATTERY_CHANGED_FILTER =
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-
- protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
-
- private static final long BROADCAST_TIMEOUT_MS = 5_000;
-
- protected Context mContext;
- protected Instrumentation mInstrumentation;
- protected ConnectivityManager mCm;
- protected int mUid;
- private int mMyUid;
- private @Nullable String mCustomUrl;
- private MyServiceClient mServiceClient;
- private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
- private PowerManager mPowerManager;
- private PowerManager.WakeLock mLock;
-
- @Rule
- public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
- .around(new MeterednessConfigurationRule());
-
- protected void setUp() throws Exception {
- mInstrumentation = getInstrumentation();
- mContext = getContext();
- mCm = getConnectivityManager();
- mDeviceIdleDeviceConfigStateHelper =
- new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_DEVICE_IDLE);
- mUid = getUid(TEST_APP2_PKG);
- mMyUid = getUid(mContext.getPackageName());
- mServiceClient = new MyServiceClient(mContext);
-
- final Bundle args = InstrumentationRegistry.getArguments();
- mCustomUrl = args.getString(ARG_CONNECTION_CHECK_CUSTOM_URL);
- if (mCustomUrl != null) {
- Log.d(TAG, "Using custom URL " + mCustomUrl + " for network checks");
- }
-
- final int bindPriorityFlags;
- if (Boolean.valueOf(args.getString(ARG_WAIVE_BIND_PRIORITY, "false"))) {
- bindPriorityFlags = Context.BIND_WAIVE_PRIORITY;
- } else {
- bindPriorityFlags = Context.BIND_NOT_FOREGROUND;
- }
- mServiceClient.bind(bindPriorityFlags);
-
- mPowerManager = mContext.getSystemService(PowerManager.class);
- executeShellCommand("cmd netpolicy start-watching " + mUid);
- // Some of the test cases assume that Data saver mode is initially disabled, which might not
- // always be the case. Therefore, explicitly disable it before running the tests.
- // Invoke setRestrictBackgroundInternal() directly instead of going through
- // setRestrictBackground(), as some devices do not fully support the Data saver mode but
- // still have certain parts of it enabled by default.
- setRestrictBackgroundInternal(false);
- setAppIdle(false);
- mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-
- Log.i(TAG, "Apps status:\n"
- + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
- + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
- }
-
- protected void tearDown() throws Exception {
- executeShellCommand("cmd netpolicy stop-watching");
- mServiceClient.unbind();
- final PowerManager.WakeLock lock = mLock;
- if (null != lock && lock.isHeld()) lock.release();
- }
-
- /**
- * Check if the feature blocking network for top_sleeping and lower priority proc-states is
- * enabled. This is a manual check because the feature flag infrastructure may not be available
- * in all the branches that will get this code.
- * TODO: b/322115994 - Use @RequiresFlagsEnabled with
- * Flags.FLAG_NETWORK_BLOCKED_FOR_TOP_SLEEPING_AND_ABOVE once the tests are moved to cts.
- */
- protected boolean isNetworkBlockedForTopSleepingAndAbove() {
- if (!SdkLevel.isAtLeastV()) {
- return false;
- }
- final String output = executeShellCommand("device_config get backstage_power"
- + " com.android.server.net.network_blocked_for_top_sleeping_and_above");
- return Boolean.parseBoolean(output);
- }
-
- /**
- * Check if the flag to use different delays for sensitive proc-states is enabled.
- * This is a manual check because the feature flag infrastructure may not be available
- * in all the branches that will get this code.
- * TODO: b/322115994 - Use @RequiresFlagsEnabled with
- * Flags.FLAG_USE_DIFFERENT_DELAYS_FOR_BACKGROUND_CHAIN once the tests are moved to cts.
- */
- private boolean useDifferentDelaysForBackgroundChain() {
- if (!SdkLevel.isAtLeastV()) {
- return false;
- }
- final String output = executeShellCommand("device_config get backstage_power"
- + " com.android.server.net.use_different_delays_for_background_chain");
- return Boolean.parseBoolean(output);
- }
-
- protected int getUid(String packageName) throws Exception {
- return mContext.getPackageManager().getPackageUid(packageName, 0);
- }
-
- protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
- assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
- assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
- }
-
- protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
- throws Exception {
- int attempts = 0;
- int count = 0;
- final int maxAttempts = 5;
- do {
- attempts++;
- count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
- assertFalse("Expected count " + expectedCount + " but actual is " + count,
- count > expectedCount);
- if (count == expectedCount) {
- break;
- }
- Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
- + attempts + " attempts; sleeping "
- + SLEEP_TIME_SEC + " seconds before trying again");
- // No sleep after the last turn
- if (attempts <= maxAttempts) {
- SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
- }
- } while (attempts <= maxAttempts);
- assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
- + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
- }
-
- protected void assertSnoozeWarningNotReceived() throws Exception {
- // Wait for a while to take broadcast queue delays into account
- SystemClock.sleep(BROADCAST_TIMEOUT_MS);
- assertEquals(0, getNumberBroadcastsReceived(DYNAMIC_RECEIVER, ACTION_SNOOZE_WARNING));
- }
-
- protected String sendOrderedBroadcast(Intent intent) throws Exception {
- return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
- }
-
- protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
- final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
- Log.d(TAG, "Sending ordered broadcast: " + intent);
- mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- final String resultData = getResultData();
- if (resultData == null) {
- Log.e(TAG, "Received null data from ordered intent");
- // Offer an empty string so that the code waiting for the result can return.
- result.offer(EMPTY_STRING);
- return;
- }
- result.offer(resultData);
- }
- }, null, 0, null, null);
-
- final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
- Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
- return resultData;
- }
-
- protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
- return mServiceClient.getCounters(receiverName, action);
- }
-
- protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
- final String status = mServiceClient.getRestrictBackgroundStatus();
- assertNotNull("didn't get API status from app2", status);
- assertEquals(restrictBackgroundValueToString(expectedStatus),
- restrictBackgroundValueToString(Integer.parseInt(status)));
- }
-
- /**
- * @deprecated The definition of "background" can be ambiguous. Use separate calls to
- * {@link #assertProcessStateBelow(int)} with
- * {@link #assertNetworkAccess(boolean, boolean, String)} to be explicit, instead.
- */
- @Deprecated
- protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(expectAllowed, false, null);
- }
-
- protected void assertTopNetworkAccess(boolean expectAllowed) throws Exception {
- assertTopState();
- assertNetworkAccess(expectAllowed, true /* needScreenOn */);
- }
-
- protected void assertForegroundServiceNetworkAccess() throws Exception {
- assertForegroundServiceState();
- assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
- }
-
- /**
- * Asserts that an app always have access while on foreground or running a foreground service.
- *
- * <p>This method will launch an activity, a foreground service to make
- * the assertion, but will finish the activity / stop the service afterwards.
- */
- protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
- // Checks foreground first.
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- finishActivity();
-
- // Then foreground service
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- stopForegroundService();
- }
-
- protected void assertExpeditedJobHasNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB);
- finishExpeditedJob();
- }
-
- protected void assertExpeditedJobHasNoNetworkAccess() throws Exception {
- launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB, false);
- finishExpeditedJob();
- }
-
- /**
- * Asserts that the process state of the test app is below, in priority, to the given
- * {@link android.app.ActivityManager.ProcessState}.
- */
- protected final void assertProcessStateBelow(int processState) throws Exception {
- assertProcessState(ps -> ps.state > processState, null);
- }
-
- protected final void assertTopState() throws Exception {
- assertProcessState(ps -> ps.state == PROCESS_STATE_TOP, () -> turnScreenOn());
- }
-
- protected final void assertForegroundServiceState() throws Exception {
- assertProcessState(ps -> ps.state == PROCESS_STATE_FOREGROUND_SERVICE, null);
- }
-
- private void assertProcessState(Predicate<ProcessState> statePredicate,
- ThrowingRunnable onRetry) throws Exception {
- final int maxTries = 30;
- ProcessState state = null;
- for (int i = 1; i <= maxTries; i++) {
- if (onRetry != null) {
- onRetry.run();
- }
- state = getProcessStateByUid(mUid);
- Log.v(TAG, "assertProcessState(): status for app2 (" + mUid + ") on attempt #" + i
- + ": " + state);
- if (statePredicate.test(state)) {
- return;
- }
- Log.i(TAG, "App not in desired process state on attempt #" + i
- + "; sleeping 1s before trying again");
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail("App2 (" + mUid + ") is not in the desired process state after " + maxTries
- + " attempts: " + state);
- }
-
- /**
- * Asserts whether the active network is available or not. If the network is unavailable, also
- * checks whether it is blocked by the expected error.
- *
- * @param expectAllowed expect background network access to be allowed or not.
- * @param expectedUnavailableError the expected error when {@code expectAllowed} is false. It's
- * meaningful only when the {@code expectAllowed} is 'false'.
- * Throws an IllegalArgumentException when {@code expectAllowed}
- * is true and this parameter is not null. When the
- * {@code expectAllowed} is 'false' and this parameter is null,
- * this function does not compare error type of the networking
- * access failure.
- */
- protected void assertNetworkAccess(boolean expectAllowed, String expectedUnavailableError)
- throws Exception {
- if (expectAllowed && expectedUnavailableError != null) {
- throw new IllegalArgumentException("expectedUnavailableError is not null");
- }
- assertNetworkAccess(expectAllowed, false, expectedUnavailableError);
- }
-
- /**
- * Asserts whether the active network is available or not.
- */
- private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
- throws Exception {
- assertNetworkAccess(expectAvailable, needScreenOn, null);
- }
-
- private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn,
- @Nullable final String expectedUnavailableError) throws Exception {
- final int maxTries = 5;
- String error = null;
- int timeoutMs = 500;
-
- for (int i = 1; i <= maxTries; i++) {
- error = checkNetworkAccess(expectAvailable, expectedUnavailableError);
-
- if (error == null) return;
-
- // TODO: ideally, it should retry only when it cannot connect to an external site,
- // or no retry at all! But, currently, the initial change fails almost always on
- // battery saver tests because the netd changes are made asynchronously.
- // Once b/27803922 is fixed, this retry mechanism should be revisited.
-
- Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
- + " on attempt #" + i + ": " + error + "\n"
- + "Sleeping " + timeoutMs + "ms before trying again");
- if (needScreenOn) {
- turnScreenOn();
- }
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(timeoutMs);
- }
- // Exponential back-off.
- timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
- }
- fail("Invalid state for " + mUid + "; expectAvailable=" + expectAvailable + " after "
- + maxTries + " attempts.\nLast error: " + error);
- }
-
- /**
- * Asserts whether the network is blocked by accessing bpf maps if command-line tool supports.
- */
- void assertNetworkAccessBlockedByBpf(boolean expectBlocked, int uid, boolean metered) {
- final String result;
- try {
- result = executeShellCommand(
- "cmd network_stack is-uid-networking-blocked " + uid + " " + metered);
- } catch (AssertionError e) {
- // If NetworkStack is too old to support this command, ignore and continue
- // this test to verify other parts.
- if (e.getMessage().contains("No shell command implementation.")) {
- return;
- }
- throw e;
- }
-
- // Tethering module is too old.
- if (result.contains("API is unsupported")) {
- return;
- }
-
- assertEquals(expectBlocked, parseBooleanOrThrow(result.trim()));
- }
-
- /**
- * Similar to {@link Boolean#parseBoolean} but throws when the input
- * is unexpected instead of returning false.
- */
- private static boolean parseBooleanOrThrow(@NonNull String s) {
- // Don't use Boolean.parseBoolean
- if ("true".equalsIgnoreCase(s)) return true;
- if ("false".equalsIgnoreCase(s)) return false;
- throw new IllegalArgumentException("Unexpected: " + s);
- }
-
- /**
- * Checks whether the network is available as expected.
- *
- * @return error message with the mismatch (or empty if assertion passed).
- */
- private String checkNetworkAccess(boolean expectAvailable,
- @Nullable final String expectedUnavailableError) throws Exception {
- final NetworkCheckResult checkResult = mServiceClient.checkNetworkStatus(mCustomUrl);
- return checkForAvailabilityInNetworkCheckResult(checkResult, expectAvailable,
- expectedUnavailableError);
- }
-
- private String checkForAvailabilityInNetworkCheckResult(NetworkCheckResult networkCheckResult,
- boolean expectAvailable, @Nullable final String expectedUnavailableError) {
- assertNotNull("NetworkCheckResult from app2 is null", networkCheckResult);
-
- final NetworkInfo networkInfo = networkCheckResult.networkInfo;
- assertNotNull("NetworkInfo from app2 is null", networkInfo);
-
- final State state = networkInfo.getState();
- final DetailedState detailedState = networkInfo.getDetailedState();
-
- final boolean connected = networkCheckResult.connected;
- final String connectionCheckDetails = networkCheckResult.details;
-
- final StringBuilder errors = new StringBuilder();
- final State expectedState;
- final DetailedState expectedDetailedState;
- if (expectAvailable) {
- expectedState = State.CONNECTED;
- expectedDetailedState = DetailedState.CONNECTED;
- } else {
- expectedState = State.DISCONNECTED;
- expectedDetailedState = DetailedState.BLOCKED;
- }
-
- if (expectAvailable != connected) {
- errors.append(String.format("External site connection failed: expected %s, got %s\n",
- expectAvailable, connected));
- }
- if (expectedState != state || expectedDetailedState != detailedState) {
- errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
- expectedState, expectedDetailedState, state, detailedState));
- } else if (!expectAvailable && (expectedUnavailableError != null)
- && !connectionCheckDetails.contains(expectedUnavailableError)) {
- errors.append("Connection unavailable reason mismatch: expected "
- + expectedUnavailableError + "\n");
- }
-
- if (errors.length() > 0) {
- errors.append("\tnetworkInfo: " + networkInfo + "\n");
- errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
- }
- return errors.length() == 0 ? null : errors.toString();
- }
-
- /**
- * Runs a Shell command which is not expected to generate output.
- */
- protected void executeSilentShellCommand(String command) {
- final String result = executeShellCommand(command);
- assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
- }
-
- /**
- * Asserts the result of a command, wait and re-running it a couple times if necessary.
- */
- protected void assertDelayedShellCommand(String command, final String expectedResult)
- throws Exception {
- assertDelayedShellCommand(command, 5, 1, expectedResult);
- }
-
- protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
- final String expectedResult) throws Exception {
- assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
-
- @Override
- public boolean isExpected(String result) {
- return expectedResult.equals(result);
- }
-
- @Override
- public String getExpected() {
- return expectedResult;
- }
- });
- }
-
- protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
- ExpectResultChecker checker) throws Exception {
- String result = "";
- for (int i = 1; i <= maxTries; i++) {
- result = executeShellCommand(command).trim();
- if (checker.isExpected(result)) return;
- Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
- + checker.getExpected() + "' on attempt #" + i
- + "; sleeping " + napTimeSeconds + "s before trying again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
- }
- }
- fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
- + maxTries
- + " attempts. Last result: '" + result + "'");
- }
-
- protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, true);
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is whitelisted, it should not be blacklisted.
- assertRestrictBackgroundBlacklist(uid, false);
- }
-
- protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, false);
- }
-
- protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("restrict-background-whitelist", uid, expected);
- }
-
- protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
- assertRestrictBackgroundBlacklist(uid, true);
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is blacklisted, it should not be whitelisted.
- assertRestrictBackgroundWhitelist(uid, false);
- }
-
- protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
- assertRestrictBackgroundBlacklist(uid, false);
- }
-
- protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("restrict-background-blacklist", uid, expected);
- }
-
- protected void addAppIdleWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
- assertAppIdleWhitelist(uid, true);
- }
-
- protected void removeAppIdleWhitelist(int uid) throws Exception {
- executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
- assertAppIdleWhitelist(uid, false);
- }
-
- protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
- assertRestrictBackground("app-idle-whitelist", uid, expected);
- }
-
- private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
- final int maxTries = 5;
- boolean actual = false;
- final String expectedUid = Integer.toString(uid);
- String uids = "";
- for (int i = 1; i <= maxTries; i++) {
- final String output =
- executeShellCommand("cmd netpolicy list " + list);
- uids = output.split(":")[1];
- for (String candidate : uids.split(" ")) {
- actual = candidate.trim().equals(expectedUid);
- if (expected == actual) {
- return;
- }
- }
- Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
- + expected + ", got " + actual + "); sleeping 1s before polling again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
- + ". Full list: " + uids);
- }
-
- protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
- throws Exception {
- Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
- executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
- }
-
- protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
- assertPowerSaveModeWhitelist(packageName, true);
- }
-
- protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
- assertPowerSaveModeWhitelist(packageName, false);
- }
-
- protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
- assertPowerSaveModeExceptIdleWhitelist(packageName, true);
- }
-
- protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Removing package " + packageName
- + " from power-save-mode-except-idle whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
- assertPowerSaveModeExceptIdleWhitelist(packageName, false);
- }
-
- protected void turnBatteryOn() throws Exception {
- executeSilentShellCommand("cmd battery unplug");
- executeSilentShellCommand("cmd battery set status "
- + BatteryManager.BATTERY_STATUS_DISCHARGING);
- assertBatteryState(false);
- }
-
- protected void turnBatteryOff() throws Exception {
- executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
- executeSilentShellCommand("cmd battery set level 100");
- executeSilentShellCommand("cmd battery set status "
- + BatteryManager.BATTERY_STATUS_CHARGING);
- assertBatteryState(true);
- }
-
- protected void resetBatteryState() {
- BatteryUtils.runDumpsysBatteryReset();
- }
-
- private void assertBatteryState(boolean pluggedIn) throws Exception {
- final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
- while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
- Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
- }
- if (isDevicePluggedIn() != pluggedIn) {
- fail("Timed out waiting for the plugged-in state to change,"
- + " expected pluggedIn: " + pluggedIn);
- }
- }
-
- private boolean isDevicePluggedIn() {
- final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
- return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
- }
-
- protected void turnScreenOff() throws Exception {
- if (!mLock.isHeld()) mLock.acquire();
- executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
- }
-
- protected void turnScreenOn() throws Exception {
- executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
- if (mLock.isHeld()) mLock.release();
- executeSilentShellCommand("wm dismiss-keyguard");
- }
-
- protected void setBatterySaverMode(boolean enabled) throws Exception {
- if (!isBatterySaverSupported()) {
- return;
- }
- Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
- if (enabled) {
- turnBatteryOn();
- AmUtils.waitForBroadcastBarrier();
- executeSilentShellCommand("cmd power set-mode 1");
- } else {
- executeSilentShellCommand("cmd power set-mode 0");
- turnBatteryOff();
- AmUtils.waitForBroadcastBarrier();
- }
- }
-
- protected void setDozeMode(boolean enabled) throws Exception {
- if (!isDozeModeSupported()) {
- return;
- }
-
- Log.i(TAG, "Setting Doze Mode to " + enabled);
- if (enabled) {
- turnBatteryOn();
- turnScreenOff();
- executeShellCommand("dumpsys deviceidle force-idle deep");
- } else {
- turnScreenOn();
- turnBatteryOff();
- executeShellCommand("dumpsys deviceidle unforce");
- }
- assertDozeMode(enabled);
- }
-
- protected void assertDozeMode(boolean enabled) throws Exception {
- assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
- }
-
- protected void stopApp() {
- executeSilentShellCommand("am stop-app " + TEST_APP2_PKG);
- }
-
- protected void setAppIdle(boolean isIdle) throws Exception {
- setAppIdleNoAssert(isIdle);
- assertAppIdle(isIdle);
- }
-
- protected void setAppIdleNoAssert(boolean isIdle) throws Exception {
- if (!isAppStandbySupported()) {
- return;
- }
- Log.i(TAG, "Setting app idle to " + isIdle);
- final String bucketName = isIdle ? "rare" : "active";
- executeSilentShellCommand("am set-standby-bucket " + TEST_APP2_PKG + " " + bucketName);
- }
-
- protected void assertAppIdle(boolean isIdle) throws Exception {
- try {
- assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
- 30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + isIdle);
- } catch (Throwable e) {
- throw e;
- }
- }
-
- /**
- * Starts a service that will register a broadcast receiver to receive
- * {@code RESTRICT_BACKGROUND_CHANGE} intents.
- * <p>
- * The service must run in a separate app because otherwise it would be killed every time
- * {@link #runDeviceTests(String, String)} is executed.
- */
- protected void registerBroadcastReceiver() throws Exception {
- mServiceClient.registerBroadcastReceiver();
-
- final Intent intent = new Intent(ACTION_RECEIVER_READY)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- // Wait until receiver is ready.
- final int maxTries = 10;
- for (int i = 1; i <= maxTries; i++) {
- final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
- Log.d(TAG, "app2 receiver acked: " + message);
- if (message != null) {
- return;
- }
- Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
- // No sleep after the last turn
- if (i < maxTries) {
- SystemClock.sleep(SECOND_IN_MS);
- }
- }
- fail("app2 receiver is not ready in " + mUid);
- }
-
- protected void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
- throws Exception {
- Log.i(TAG, "Registering network callback for request: " + request);
- mServiceClient.registerNetworkCallback(request, cb);
- }
-
- protected void unregisterNetworkCallback() throws Exception {
- mServiceClient.unregisterNetworkCallback();
- }
-
- /**
- * Registers a {@link NotificationListenerService} implementation that will execute the
- * notification actions right after the notification is sent.
- */
- protected void registerNotificationListenerService() throws Exception {
- executeShellCommand("cmd notification allow_listener "
- + MyNotificationListenerService.getId());
- final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
- final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
- assertTrue(listenerComponent + " has not been granted access",
- nm.isNotificationListenerAccessGranted(listenerComponent));
- }
-
- protected void setPendingIntentAllowlistDuration(long durationMs) {
- mDeviceIdleDeviceConfigStateHelper.set("notification_allowlist_duration_ms",
- String.valueOf(durationMs));
- }
-
- protected void resetDeviceIdleSettings() {
- mDeviceIdleDeviceConfigStateHelper.restoreOriginalValues();
- }
-
- protected void launchActivity() throws Exception {
- turnScreenOn();
- final CountDownLatch latch = new CountDownLatch(1);
- final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
- final RemoteCallback callback = new RemoteCallback(result -> latch.countDown());
- launchIntent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
- mContext.startActivity(launchIntent);
- // There might be a race when app2 is launched but ACTION_FINISH_ACTIVITY has not registered
- // before test calls finishActivity(). When the issue is happened, there is no way to fix
- // it, so have a callback design to make sure that the app is launched completely and
- // ACTION_FINISH_ACTIVITY will be registered before leaving this method.
- if (!latch.await(LAUNCH_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Timed out waiting for launching activity");
- }
- }
-
- protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
- launchComponentAndAssertNetworkAccess(type, true);
- }
-
- protected void launchComponentAndAssertNetworkAccess(int type, boolean expectAvailable)
- throws Exception {
- if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
- startForegroundService();
- assertForegroundServiceNetworkAccess();
- } else if (type == TYPE_COMPONENT_ACTIVTIY) {
- turnScreenOn();
- final CountDownLatch latch = new CountDownLatch(1);
- final Intent launchIntent = getIntentForComponent(type);
- final Bundle extras = new Bundle();
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
- new AtomicReference<>();
- extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
- extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
- extras.putString(KEY_CUSTOM_URL, mCustomUrl);
- launchIntent.putExtras(extras);
- mContext.startActivity(launchIntent);
- if (latch.await(ACTIVITY_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get().first;
- final NetworkCheckResult networkCheckResult = result.get().second;
- if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInNetworkCheckResult(
- networkCheckResult, expectAvailable,
- null /* expectedUnavailableError */);
- if (error != null) {
- fail("Network is not available for activity in app2 (" + mUid + "): "
- + error);
- }
- } else if (resultCode == INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE) {
- Log.d(TAG, networkCheckResult.details);
- // App didn't come to foreground when the activity is started, so try again.
- assertTopNetworkAccess(true);
- } else {
- fail("Unexpected resultCode=" + resultCode
- + "; networkCheckResult=[" + networkCheckResult + "]");
- }
- } else {
- fail("Timed out waiting for network availability status from app2's activity ("
- + mUid + ")");
- }
- } else if (type == TYPE_EXPEDITED_JOB) {
- final Bundle extras = new Bundle();
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result =
- new AtomicReference<>();
- final CountDownLatch latch = new CountDownLatch(1);
- extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
- extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
- extras.putString(KEY_CUSTOM_URL, mCustomUrl);
- final JobInfo jobInfo = new JobInfo.Builder(TEST_JOB_ID, TEST_JOB_COMPONENT)
- .setExpedited(true)
- .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
- .setTransientExtras(extras)
- .build();
- assertEquals("Error scheduling " + jobInfo,
- RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
- forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
- if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- final int resultCode = result.get().first;
- final NetworkCheckResult networkCheckResult = result.get().second;
- if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
- final String error = checkForAvailabilityInNetworkCheckResult(
- networkCheckResult, expectAvailable,
- null /* expectedUnavailableError */);
- if (error != null) {
- Log.d(TAG, "Network state is unexpected, checking again. " + error);
- // Right now we could end up in an unexpected state if expedited job
- // doesn't have network access immediately after starting, so check again.
- assertNetworkAccess(expectAvailable, false /* needScreenOn */);
- }
- } else {
- fail("Unexpected resultCode=" + resultCode
- + "; networkCheckResult=[" + networkCheckResult + "]");
- }
- } else {
- fail("Timed out waiting for network availability status from app2's expedited job ("
- + mUid + ")");
- }
- } else {
- throw new IllegalArgumentException("Unknown type: " + type);
- }
- }
-
- protected void startActivity() throws Exception {
- final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
- mContext.startActivity(launchIntent);
- }
-
- private void startForegroundService() throws Exception {
- final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
- mContext.startForegroundService(launchIntent);
- assertForegroundServiceState();
- }
-
- private Intent getIntentForComponent(int type) {
- final Intent intent = new Intent();
- if (type == TYPE_COMPONENT_ACTIVTIY) {
- intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
- } else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
- intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
- .setFlags(1);
- } else {
- fail("Unknown type: " + type);
- }
- return intent;
- }
-
- protected void stopForegroundService() throws Exception {
- executeShellCommand(String.format("am startservice -f 2 %s/%s",
- TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
- // NOTE: cannot assert state because it depends on whether activity was on top before.
- }
-
- private Binder getNewNetworkStateObserver(final CountDownLatch latch,
- final AtomicReference<Pair<Integer, NetworkCheckResult>> result) {
- return new INetworkStateObserver.Stub() {
- @Override
- public void onNetworkStateChecked(int resultCode,
- NetworkCheckResult networkCheckResult) {
- result.set(Pair.create(resultCode, networkCheckResult));
- latch.countDown();
- }
- };
- }
-
- /**
- * Finishes an activity on app2 so its process is demoted from foreground status.
- */
- protected void finishActivity() throws Exception {
- final Intent intent = new Intent(ACTION_FINISH_ACTIVITY)
- .setPackage(TEST_APP2_PKG)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- sendOrderedBroadcast(intent);
- }
-
- /**
- * Finishes the expedited job on app2 so its process is demoted from foreground status.
- */
- private void finishExpeditedJob() throws Exception {
- final Intent intent = new Intent(ACTION_FINISH_JOB)
- .setPackage(TEST_APP2_PKG)
- .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- sendOrderedBroadcast(intent);
- }
-
- protected void sendNotification(int notificationId, String notificationType) throws Exception {
- Log.d(TAG, "Sending notification broadcast (id=" + notificationId
- + ", type=" + notificationType);
- mServiceClient.sendNotification(notificationId, notificationType);
- }
-
- protected String showToast() {
- final Intent intent = new Intent(ACTION_SHOW_TOAST);
- intent.setPackage(TEST_APP2_PKG);
- Log.d(TAG, "Sending request to show toast");
- try {
- return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
- } catch (Exception e) {
- return "";
- }
- }
-
- private ProcessState getProcessStateByUid(int uid) throws Exception {
- return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
- }
-
- private static class ProcessState {
- private final String fullState;
- final int state;
-
- ProcessState(String fullState) {
- this.fullState = fullState;
- try {
- this.state = Integer.parseInt(fullState.split(" ")[0]);
- } catch (Exception e) {
- throw new IllegalArgumentException("Could not parse " + fullState);
- }
- }
-
- @Override
- public String toString() {
- return fullState;
- }
- }
-
- /**
- * Helper class used to assert the result of a Shell command.
- */
- protected static interface ExpectResultChecker {
- /**
- * Checkes whether the result of the command matched the expectation.
- */
- boolean isExpected(String result);
- /**
- * Gets the expected result so it's displayed on log and failure messages.
- */
- String getExpected();
- }
-
- protected void setRestrictedNetworkingMode(boolean enabled) throws Exception {
- executeSilentShellCommand(
- "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
- assertRestrictedNetworkingModeState(enabled);
- }
-
- protected void assertRestrictedNetworkingModeState(boolean enabled) throws Exception {
- assertDelayedShellCommand("cmd netpolicy get restricted-mode",
- "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
deleted file mode 100644
index 6b802f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
deleted file mode 100644
index 2e725ae..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/AppIdleNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
deleted file mode 100644
index 2e421f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
deleted file mode 100644
index 0be5644..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/BatterySaverModeNonMeteredTest.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
deleted file mode 100644
index bfccce9..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ConnOnActivityStartTest.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2022 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.cts.netpolicy.hostside;
-
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getUiDevice;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class ConnOnActivityStartTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final int TEST_ITERATION_COUNT = 5;
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
- resetDeviceState();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
- stopApp();
- resetDeviceState();
- }
-
- private void resetDeviceState() throws Exception {
- resetBatteryState();
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
- setDozeMode(false);
- }
-
-
- @Test
- @RequiredProperties({BATTERY_SAVER_MODE})
- public void testStartActivity_batterySaver() throws Exception {
- setBatterySaverMode(true);
- assertNetworkAccess(false, null);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver", null);
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
- public void testStartActivity_dataSaver() throws Exception {
- setRestrictBackground(true);
- assertNetworkAccess(false, null);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver", null);
- }
-
- @Test
- @RequiredProperties({DOZE_MODE})
- public void testStartActivity_doze() throws Exception {
- setDozeMode(true);
- assertNetworkAccess(false, null);
- // TODO (235284115): We need to turn on Doze every time before starting
- // the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_doze", null);
- }
-
- @Test
- @RequiredProperties({APP_STANDBY_MODE})
- public void testStartActivity_appStandby() throws Exception {
- turnBatteryOn();
- setAppIdle(true);
- assertNetworkAccess(false, null);
- // TODO (235284115): We need to put the app into app standby mode every
- // time before starting the activity.
- assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby", null);
- }
-
- @Test
- public void testStartActivity_default() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
- assertLaunchedActivityHasNetworkAccess("testStartActivity_default", () -> {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- });
- }
-
- private void assertLaunchedActivityHasNetworkAccess(String testName,
- ThrowingRunnable onBeginIteration) throws Exception {
- for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
- if (onBeginIteration != null) {
- onBeginIteration.run();
- }
- Log.i(TAG, testName + " start #" + i);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- getUiDevice().pressHome();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- Log.i(TAG, testName + " end #" + i);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
deleted file mode 100644
index 66e0d00..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataSaverModeTest.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
-
-import static com.android.compatibility.common.util.FeatureUtil.isTV;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NO_DATA_SAVER_MODE;
-
-import static org.junit.Assert.fail;
-
-import androidx.test.filters.LargeTest;
-
-import com.android.compatibility.common.util.CddTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
-@LargeTest
-public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
- "com.android.providers.downloads"
- };
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- setRestrictBackground(false);
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
-
- registerBroadcastReceiver();
- assertRestrictBackgroundChangedReceived(0);
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setRestrictBackground(false);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_disabled() throws Exception {
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
-
- // Verify status is always disabled, never whitelisted
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(0);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(2);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
-
- removeRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(3);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_enabled() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- // Make sure foreground app doesn't lose access upon enabling Data Saver.
- setRestrictBackground(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setRestrictBackground(true);
- assertTopNetworkAccess(true);
-
- // Although it should not have access while the screen is off.
- turnScreenOff();
- assertBackgroundNetworkAccess(false);
- turnScreenOn();
- // On some TVs, it is possible that the activity on top may change after the screen is
- // turned off and on again, so relaunch the activity in the test app again.
- if (isTV()) {
- startActivity();
- }
- assertTopNetworkAccess(true);
-
- // Goes back to background state.
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // Make sure foreground service doesn't lose access upon enabling Data Saver.
- setRestrictBackground(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
- setRestrictBackground(true);
- assertForegroundServiceNetworkAccess();
- stopForegroundService();
- assertBackgroundNetworkAccess(false);
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
- addRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- assertsForegroundAlwaysHasNetworkAccess();
- assertRestrictBackgroundChangedReceived(1);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
-
- // UID policies live by the Highlander rule: "There can be only one".
- // Hence, if app is whitelisted, it should not be blacklisted anymore.
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(2);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- addRestrictBackgroundWhitelist(mUid);
- assertRestrictBackgroundChangedReceived(3);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_WHITELISTED);
-
- // Check status after removing blacklist.
- // ...re-enables first
- addRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(4);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- // ... remove blacklist - access's still rejected because Data Saver is on
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(4);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- // ... finally, disable Data Saver
- setRestrictBackground(false);
- assertRestrictBackgroundChangedReceived(5);
- assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
- assertsForegroundAlwaysHasNetworkAccess();
- }
-
- @Test
- public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
- final StringBuilder error = new StringBuilder();
- for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
- int uid = -1;
- try {
- uid = getUid(packageName);
- assertRestrictBackgroundWhitelist(uid, true);
- } catch (Throwable t) {
- error.append("\nFailed for '").append(packageName).append("'");
- if (uid > 0) {
- error.append(" (uid ").append(uid).append(")");
- }
- error.append(": ").append(t).append("\n");
- }
- }
- if (error.length() > 0) {
- fail(error.toString());
- }
- }
-
- @RequiredProperties({NO_DATA_SAVER_MODE})
- @CddTest(requirement="7.4.7/C-2-2")
- @Test
- public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(0);
-
- setRestrictBackground(false);
- assertRestrictBackgroundChangedReceived(0);
-
- setRestrictBackground(true);
- assertRestrictBackgroundChangedReceived(0);
- }
-
- private void assertDataSaverStatusOnBackground(int expectedStatus) throws Exception {
- assertRestrictBackgroundStatus(expectedStatus);
- assertBackgroundNetworkAccess(expectedStatus != RESTRICT_BACKGROUND_STATUS_ENABLED);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
deleted file mode 100644
index 69ca206..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DataWarningReceiverTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.clearSnoozeTimestamps;
-
-import android.content.pm.PackageManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.SubscriptionPlan;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.By;
-import androidx.test.uiautomator.Direction;
-import androidx.test.uiautomator.UiDevice;
-import androidx.test.uiautomator.UiObject2;
-import androidx.test.uiautomator.Until;
-
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.UiAutomatorUtils2;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.time.Period;
-import java.time.ZonedDateTime;
-import java.util.Arrays;
-import java.util.List;
-
-public class DataWarningReceiverTest extends AbstractRestrictBackgroundNetworkTestCase {
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- clearSnoozeTimestamps();
- registerBroadcastReceiver();
- turnScreenOn();
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
- }
-
- @Test
- public void testSnoozeWarningNotReceived() throws Exception {
- Assume.assumeTrue("Feature not supported: " + PackageManager.FEATURE_TELEPHONY,
- mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
- final SubscriptionManager sm = mContext.getSystemService(SubscriptionManager.class);
- final int subId = SubscriptionManager.getDefaultDataSubscriptionId();
- Assume.assumeTrue("Valid subId not found",
- subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID);
-
- setSubPlanOwner(subId, TEST_PKG);
- final List<SubscriptionPlan> originalPlans = sm.getSubscriptionPlans(subId);
- try {
- // In NetworkPolicyManagerService class, we set the data warning bytes to 90% of
- // data limit bytes. So, create the subscription plan in such a way this data warning
- // threshold is already reached.
- final SubscriptionPlan plan = SubscriptionPlan.Builder
- .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
- Period.ofMonths(1))
- .setTitle("CTS")
- .setDataLimit(1_000_000_000, SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
- .setDataUsage(999_000_000, System.currentTimeMillis())
- .build();
- sm.setSubscriptionPlans(subId, Arrays.asList(plan));
- final UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
- uiDevice.openNotification();
- try {
- final UiObject2 uiObject = UiAutomatorUtils2.waitFindObject(
- By.text("Data warning"));
- Assume.assumeNotNull(uiObject);
- uiObject.wait(Until.clickable(true), 10_000L);
- uiObject.getParent().swipe(Direction.RIGHT, 1.0f);
- } catch (Throwable t) {
- Assume.assumeNoException(
- "Error occurred while finding and swiping the notification", t);
- }
- assertSnoozeWarningNotReceived();
- uiDevice.pressHome();
- } finally {
- sm.setSubscriptionPlans(subId, originalPlans);
- setSubPlanOwner(subId, "");
- }
- }
-
- private static void setSubPlanOwner(int subId, String packageName) throws Exception {
- SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(),
- "cmd netpolicy set sub-plan-owner " + subId + " " + packageName);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
deleted file mode 100644
index 810fd19..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class DefaultRestrictionsMeteredTest extends AbstractDefaultRestrictionsTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
deleted file mode 100644
index fef546c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DefaultRestrictionsNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class DefaultRestrictionsNonMeteredTest extends AbstractDefaultRestrictionsTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
deleted file mode 100644
index 741dd7e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
deleted file mode 100644
index f343df5..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DozeModeNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
deleted file mode 100644
index 2dc6cc4..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/DumpOnFailureRule.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_APP2_PKG;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
-
-import android.os.Environment;
-import android.os.FileUtils;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.compatibility.common.util.OnFailureRule;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
-public class DumpOnFailureRule extends OnFailureRule {
- private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
- "CtsHostsideNetworkPolicyTests");
-
- @Override
- public void onTestFailure(Statement base, Description description, Throwable throwable) {
- if (throwable instanceof AssumptionViolatedException) {
- final String testName = description.getClassName() + "_" + description.getMethodName();
- Log.d(TAG, "Skipping test " + testName + ": " + throwable);
- return;
- }
-
- prepareDumpRootDir();
- final String shortenedTestName = getShortenedTestName(description);
- final File dumpFile = new File(mDumpDir, "dump-" + shortenedTestName);
- Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
- try (FileOutputStream out = new FileOutputStream(dumpFile)) {
- for (String cmd : new String[] {
- "dumpsys netpolicy",
- "dumpsys network_management",
- "dumpsys usagestats " + TEST_PKG + " " + TEST_APP2_PKG,
- "dumpsys usagestats appstandby",
- "dumpsys connectivity trafficcontroller",
- "dumpsys netd trafficcontroller",
- "dumpsys platform_compat", // TODO (b/279829773): Remove this dump
- "dumpsys jobscheduler " + TEST_APP2_PKG, // TODO (b/288220398): Remove this dump
- }) {
- dumpCommandOutput(out, cmd);
- }
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Error opening file: " + dumpFile, e);
- } catch (IOException e) {
- Log.e(TAG, "Error closing file: " + dumpFile, e);
- }
- final UiDevice uiDevice = UiDevice.getInstance(
- InstrumentationRegistry.getInstrumentation());
- final File screenshotFile = new File(mDumpDir, "sc-" + shortenedTestName + ".png");
- uiDevice.takeScreenshot(screenshotFile);
- final File windowHierarchyFile = new File(mDumpDir, "wh-" + shortenedTestName + ".xml");
- try {
- uiDevice.dumpWindowHierarchy(windowHierarchyFile);
- } catch (IOException e) {
- Log.e(TAG, "Error dumping window hierarchy", e);
- }
- }
-
- private String getShortenedTestName(Description description) {
- final String qualifiedClassName = description.getClassName();
- final String className = qualifiedClassName.substring(
- qualifiedClassName.lastIndexOf(".") + 1);
- final String shortenedClassName = className.chars()
- .filter(Character::isUpperCase)
- .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
- .toString();
- return shortenedClassName + "_" + description.getMethodName();
- }
-
- void dumpCommandOutput(FileOutputStream out, String cmd) {
- final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
- .getUiAutomation().executeShellCommand(cmd);
- try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
- out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
- FileUtils.copy(in, out);
- out.write("\n\n=================================================================\n\n"
- .getBytes(StandardCharsets.UTF_8));
- } catch (IOException e) {
- Log.e(TAG, "Error dumping '" + cmd + "'", e);
- }
- }
-
- void prepareDumpRootDir() {
- if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
- Log.e(TAG, "Error creating " + mDumpDir);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
deleted file mode 100644
index d56a50b..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-
-@RequiredProperties({METERED_NETWORK})
-public class ExpeditedJobMeteredTest extends AbstractExpeditedJobTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
deleted file mode 100644
index 0a776ee..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/ExpeditedJobNonMeteredTest.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class ExpeditedJobNonMeteredTest extends AbstractExpeditedJobTest {
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
deleted file mode 100644
index 4f4e68e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MeterednessConfigurationRule.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setupActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import android.util.ArraySet;
-
-import com.android.compatibility.common.util.BeforeAfterRule;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-public class MeterednessConfigurationRule extends BeforeAfterRule {
- private ThrowingRunnable mMeterednessResetter;
-
- @Override
- public void onBefore(Statement base, Description description) throws Throwable {
- final ArraySet<Property> requiredProperties
- = RequiredPropertiesRule.getRequiredProperties();
- if (requiredProperties.contains(METERED_NETWORK)) {
- configureNetworkMeteredness(true);
- } else if (requiredProperties.contains(NON_METERED_NETWORK)) {
- configureNetworkMeteredness(false);
- }
- }
-
- @Override
- public void onAfter(Statement base, Description description) throws Throwable {
- resetNetworkMeteredness();
- }
-
- public void configureNetworkMeteredness(boolean metered) throws Exception {
- mMeterednessResetter = setupActiveNetworkMeteredness(metered);
- }
-
- public void resetNetworkMeteredness() throws Exception {
- if (mMeterednessResetter != null) {
- mMeterednessResetter.run();
- mMeterednessResetter = null;
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
deleted file mode 100644
index b0fa106..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MixedModesTest.java
+++ /dev/null
@@ -1,370 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DOZE_MODE;
-import static com.android.cts.netpolicy.hostside.Property.METERED_NETWORK;
-import static com.android.cts.netpolicy.hostside.Property.NON_METERED_NETWORK;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
- * and Data Saver Mode) are applied simultaneously.
- * <p>
- * <strong>NOTE: </strong>it might sound like the test methods on this class are testing too much,
- * which would make it harder to diagnose individual failures, but the assumption is that such
- * failure most likely will happen when the restriction is tested individually as well.
- */
-public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final String TAG = "MixedModesTest";
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- // Set initial state.
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
-
- registerBroadcastReceiver();
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- try {
- setRestrictBackground(false);
- } finally {
- setBatterySaverMode(false);
- }
- }
-
- /**
- * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
- */
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
- @Test
- public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- final MeterednessConfigurationRule meterednessConfiguration
- = new MeterednessConfigurationRule();
- meterednessConfiguration.configureNetworkMeteredness(true);
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
-
- Log.v(TAG, "Not whitelisted for any.");
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
- addRestrictBackgroundWhitelist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
- Log.v(TAG, "Whitelisted for both.");
- addRestrictBackgroundWhitelist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- meterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- /**
- * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
- * networks.
- */
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
- @Test
- public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- final MeterednessConfigurationRule meterednessConfiguration
- = new MeterednessConfigurationRule();
- meterednessConfiguration.configureNetworkMeteredness(false);
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
-
- Log.v(TAG, "Not whitelisted for any.");
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
-
- Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
- addRestrictBackgroundWhitelist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- removeRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
- Log.v(TAG, "Whitelisted for both.");
- addRestrictBackgroundWhitelist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundWhitelist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(false);
- removeRestrictBackgroundBlacklist(mUid);
-
- Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
- addRestrictBackgroundBlacklist(mUid);
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
- assertsForegroundAlwaysHasNetworkAccess();
- assertBackgroundNetworkAccess(true);
- removeRestrictBackgroundBlacklist(mUid);
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- meterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- /**
- * Tests that powersave whitelists works as expected when doze and battery saver modes
- * are enabled.
- */
- @RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- setBatterySaverMode(true);
- setDozeMode(true);
-
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- } finally {
- setBatterySaverMode(false);
- setDozeMode(false);
- }
- }
-
- /**
- * Tests that powersave whitelists works as expected when doze and appIdle modes
- * are enabled.
- */
- @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
- @Test
- public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
-
- removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
- @Test
- public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- setBatterySaverMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setBatterySaverMode(false);
- }
- }
-
- /**
- * Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
- */
- @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
- @Test
- public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- // UID still shouldn't have access because of Doze.
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- removeAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
- @Test
- public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- setDozeMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setDozeMode(false);
- removeAppIdleWhitelist(mUid);
- }
- }
-
- @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- setBatterySaverMode(true);
- setAppIdle(true);
-
- try {
- assertBackgroundNetworkAccess(false);
-
- addAppIdleWhitelist(mUid);
- assertBackgroundNetworkAccess(false);
-
- addTempPowerSaveModeWhitelist(TEST_APP2_PKG, TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(true);
-
- // Wait until the whitelist duration is expired.
- SystemClock.sleep(TEMP_POWERSAVE_WHITELIST_DURATION_MS);
- assertBackgroundNetworkAccess(false);
- } finally {
- setAppIdle(false);
- setBatterySaverMode(false);
- removeAppIdleWhitelist(mUid);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
deleted file mode 100644
index 6dc9921..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyNotificationListenerService.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.app.PendingIntent.CanceledException;
-import android.app.RemoteInput;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-/**
- * NotificationListenerService implementation that executes the notification actions once they're
- * created.
- */
-public class MyNotificationListenerService extends NotificationListenerService {
- private static final String TAG = "MyNotificationListenerService";
-
- @Override
- public void onListenerConnected() {
- Log.d(TAG, "onListenerConnected()");
- }
-
- @Override
- public void onNotificationPosted(StatusBarNotification sbn) {
- Log.d(TAG, "onNotificationPosted(): " + sbn);
- if (!sbn.getPackageName().startsWith(getPackageName())) {
- Log.v(TAG, "ignoring notification from a different package");
- return;
- }
- final PendingIntentSender sender = new PendingIntentSender();
- final Notification notification = sbn.getNotification();
- if (notification.contentIntent != null) {
- sender.send("content", notification.contentIntent);
- }
- if (notification.deleteIntent != null) {
- sender.send("delete", notification.deleteIntent);
- }
- if (notification.fullScreenIntent != null) {
- sender.send("full screen", notification.fullScreenIntent);
- }
- if (notification.actions != null) {
- for (Notification.Action action : notification.actions) {
- sender.send("action", action.actionIntent);
- sender.send("action extras", action.getExtras());
- final RemoteInput[] remoteInputs = action.getRemoteInputs();
- if (remoteInputs != null && remoteInputs.length > 0) {
- for (RemoteInput remoteInput : remoteInputs) {
- sender.send("remote input extras", remoteInput.getExtras());
- }
- }
- }
- }
- sender.send("notification extras", notification.extras);
- }
-
- static String getId() {
- return String.format("%s/%s", MyNotificationListenerService.class.getPackage().getName(),
- MyNotificationListenerService.class.getName());
- }
-
- static ComponentName getComponentName() {
- return new ComponentName(MyNotificationListenerService.class.getPackage().getName(),
- MyNotificationListenerService.class.getName());
- }
-
- private static final class PendingIntentSender {
- private PendingIntent mSentIntent = null;
- private String mReason = null;
-
- private void send(String reason, PendingIntent pendingIntent) {
- if (pendingIntent == null) {
- // Could happen on action that only has extras
- Log.v(TAG, "Not sending null pending intent for " + reason);
- return;
- }
- if (mSentIntent != null || mReason != null) {
- // Sanity check: make sure test case set up just one pending intent in the
- // notification, otherwise it could pass because another pending intent caused the
- // whitelisting.
- throw new IllegalStateException("Already sent a PendingIntent (" + mSentIntent
- + ") for reason '" + mReason + "' when requested another for '" + reason
- + "' (" + pendingIntent + ")");
- }
- Log.i(TAG, "Sending pending intent for " + reason + ":" + pendingIntent);
- try {
- pendingIntent.send();
- mSentIntent = pendingIntent;
- mReason = reason;
- } catch (CanceledException e) {
- Log.w(TAG, "Pending intent " + pendingIntent + " canceled");
- }
- }
-
- private void send(String reason, Bundle extras) {
- if (extras != null) {
- for (String key : extras.keySet()) {
- Object value = extras.get(key);
- if (value instanceof PendingIntent) {
- send(reason + " with key '" + key + "'", (PendingIntent) value);
- }
- }
- }
- }
-
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
deleted file mode 100644
index 71b28f6..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/MyServiceClient.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside;
-
-import android.app.job.JobInfo;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.NetworkRequest;
-import android.os.ConditionVariable;
-import android.os.IBinder;
-import android.os.RemoteException;
-
-public class MyServiceClient {
- private static final int TIMEOUT_MS = 20_000;
- private static final String PACKAGE = MyServiceClient.class.getPackage().getName();
- private static final String APP2_PACKAGE = PACKAGE + ".app2";
- private static final String SERVICE_NAME = APP2_PACKAGE + ".MyService";
-
- private Context mContext;
- private ServiceConnection mServiceConnection;
- private volatile IMyService mService;
- private final ConditionVariable mServiceCondition = new ConditionVariable();
-
- public MyServiceClient(Context context) {
- mContext = context;
- }
-
- /**
- * Binds to a service in the test app to communicate state.
- * @param bindPriorityFlags Flags to influence the process-state of the bound app.
- */
- public void bind(int bindPriorityFlags) {
- if (mService != null) {
- throw new IllegalStateException("Already bound");
- }
- mServiceConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mService = IMyService.Stub.asInterface(service);
- mServiceCondition.open();
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mServiceCondition.close();
- mService = null;
- }
- };
-
- final Intent intent = new Intent();
- intent.setComponent(new ComponentName(APP2_PACKAGE, SERVICE_NAME));
- // Needs to use BIND_NOT_FOREGROUND so app2 does not run in
- // the same process state as app
- mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE
- | bindPriorityFlags);
- ensureServiceConnection();
- }
-
- public void unbind() {
- if (mService != null) {
- mContext.unbindService(mServiceConnection);
- }
- }
-
- private void ensureServiceConnection() {
- if (mService != null) {
- return;
- }
- mServiceCondition.block(TIMEOUT_MS);
- if (mService == null) {
- throw new IllegalStateException(
- "Could not bind to MyService service after " + TIMEOUT_MS + "ms");
- }
- }
-
- public void registerBroadcastReceiver() throws RemoteException {
- ensureServiceConnection();
- mService.registerBroadcastReceiver();
- }
-
- public int getCounters(String receiverName, String action) throws RemoteException {
- ensureServiceConnection();
- return mService.getCounters(receiverName, action);
- }
-
- /** Retrieves the network state as observed from the bound test app */
- public NetworkCheckResult checkNetworkStatus(String address) throws RemoteException {
- ensureServiceConnection();
- return mService.checkNetworkStatus(address);
- }
-
- public String getRestrictBackgroundStatus() throws RemoteException {
- ensureServiceConnection();
- return mService.getRestrictBackgroundStatus();
- }
-
- public void sendNotification(int notificationId, String notificationType)
- throws RemoteException {
- ensureServiceConnection();
- mService.sendNotification(notificationId, notificationType);
- }
-
- public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
- throws RemoteException {
- ensureServiceConnection();
- mService.registerNetworkCallback(request, cb);
- }
-
- public void unregisterNetworkCallback() throws RemoteException {
- ensureServiceConnection();
- mService.unregisterNetworkCallback();
- }
-
- public int scheduleJob(JobInfo jobInfo) throws RemoteException {
- ensureServiceConnection();
- return mService.scheduleJob(jobInfo);
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
deleted file mode 100644
index 3934cfa..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkCallbackTest.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.getActiveNetworkCapabilities;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.net.cts.util.CtsNetUtils;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.android.modules.utils.build.SdkLevel;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-
-import java.util.ArrayList;
-import java.util.Objects;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
- private Network mNetwork;
- private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
- private CtsNetUtils mCtsNetUtils;
- private static final String GOOGLE_PRIVATE_DNS_SERVER = "dns.google";
-
- @Rule
- public final MeterednessConfigurationRule mMeterednessConfiguration
- = new MeterednessConfigurationRule();
-
- enum CallbackState {
- NONE,
- AVAILABLE,
- LOST,
- BLOCKED_STATUS,
- CAPABILITIES
- }
-
- private static class CallbackInfo {
- public final CallbackState state;
- public final Network network;
- public final Object arg;
-
- CallbackInfo(CallbackState s, Network n, Object o) {
- state = s; network = n; arg = o;
- }
-
- public String toString() {
- return String.format("%s (%s) (%s)", state, network, arg);
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof CallbackInfo)) return false;
- // Ignore timeMs, since it's unpredictable.
- final CallbackInfo other = (CallbackInfo) o;
- return (state == other.state) && Objects.equals(network, other.network)
- && Objects.equals(arg, other.arg);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(state, network, arg);
- }
- }
-
- private class TestNetworkCallback extends INetworkCallback.Stub {
- private static final int TEST_CONNECT_TIMEOUT_MS = 30_000;
- private static final int TEST_CALLBACK_TIMEOUT_MS = 5_000;
-
- private final LinkedBlockingQueue<CallbackInfo> mCallbacks = new LinkedBlockingQueue<>();
-
- protected void setLastCallback(CallbackState state, Network network, Object o) {
- mCallbacks.offer(new CallbackInfo(state, network, o));
- }
-
- CallbackInfo nextCallback(int timeoutMs) {
- CallbackInfo cb = null;
- try {
- cb = mCallbacks.poll(timeoutMs, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- }
- if (cb == null) {
- fail("Did not receive callback after " + timeoutMs + "ms");
- }
- return cb;
- }
-
- CallbackInfo expectCallback(CallbackState state, Network expectedNetwork, Object o) {
- final CallbackInfo expected = new CallbackInfo(state, expectedNetwork, o);
- final CallbackInfo actual = nextCallback(TEST_CALLBACK_TIMEOUT_MS);
- assertEquals("Unexpected callback:", expected, actual);
- return actual;
- }
-
- @Override
- public void onAvailable(Network network) {
- setLastCallback(CallbackState.AVAILABLE, network, null);
- }
-
- @Override
- public void onLost(Network network) {
- setLastCallback(CallbackState.LOST, network, null);
- }
-
- @Override
- public void onBlockedStatusChanged(Network network, boolean blocked) {
- setLastCallback(CallbackState.BLOCKED_STATUS, network, blocked);
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
- setLastCallback(CallbackState.CAPABILITIES, network, cap);
- }
-
- public Network expectAvailableCallbackAndGetNetwork() {
- final CallbackInfo cb = nextCallback(TEST_CONNECT_TIMEOUT_MS);
- if (cb.state != CallbackState.AVAILABLE) {
- fail("Network is not available. Instead obtained the following callback :" + cb);
- }
- return cb.network;
- }
-
- public void drainAndWaitForIdle() {
- try {
- do {
- mCallbacks.drainTo(new ArrayList<>());
- } while (mCallbacks.poll(TEST_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS) != null);
- } catch (InterruptedException ie) {
- Log.e(TAG, "Interrupted while draining callback queue", ie);
- Thread.currentThread().interrupt();
- }
- }
-
- public void expectBlockedStatusCallback(Network expectedNetwork, boolean expectBlocked) {
- expectCallback(CallbackState.BLOCKED_STATUS, expectedNetwork, expectBlocked);
- }
-
- public void expectBlockedStatusCallbackEventually(Network expectedNetwork,
- boolean expectBlocked) {
- final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
- do {
- final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
- if (cb.state == CallbackState.BLOCKED_STATUS
- && cb.network.equals(expectedNetwork)) {
- assertEquals(expectBlocked, cb.arg);
- return;
- }
- } while (System.currentTimeMillis() <= deadline);
- fail("Didn't receive onBlockedStatusChanged()");
- }
-
- public void expectCapabilitiesCallbackEventually(Network expectedNetwork, boolean hasCap,
- int cap) {
- final long deadline = System.currentTimeMillis() + TEST_CALLBACK_TIMEOUT_MS;
- do {
- final CallbackInfo cb = nextCallback((int) (deadline - System.currentTimeMillis()));
- if (cb.state != CallbackState.CAPABILITIES
- || !expectedNetwork.equals(cb.network)
- || (hasCap != ((NetworkCapabilities) cb.arg).hasCapability(cap))) {
- Log.i("NetworkCallbackTest#expectCapabilitiesCallback",
- "Ignoring non-matching callback : " + cb);
- continue;
- }
- // Found a match, return
- return;
- } while (System.currentTimeMillis() <= deadline);
- fail("Didn't receive the expected callback to onCapabilitiesChanged(). Check the "
- + "log for a list of received callbacks, if any.");
- }
- }
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- assumeTrue(canChangeActiveNetworkMeteredness());
-
- registerBroadcastReceiver();
-
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(0);
-
- // Initial state
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
-
- // Get transports of the active network, this has to be done before changing meteredness,
- // since wifi will be disconnected when changing from non-metered to metered.
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
-
- // Mark network as metered.
- mMeterednessConfiguration.configureNetworkMeteredness(true);
-
- // Register callback, copy the capabilities from the active network to expect the "original"
- // network before disconnecting, but null out some fields to prevent over-specified.
- registerNetworkCallback(new NetworkRequest.Builder()
- .setCapabilities(networkCapabilities.setTransportInfo(null))
- .removeCapability(NET_CAPABILITY_NOT_METERED)
- .setSignalStrength(SIGNAL_STRENGTH_UNSPECIFIED).build(), mTestNetworkCallback);
- // Wait for onAvailable() callback to ensure network is available before the test
- // and store the default network.
- mNetwork = mTestNetworkCallback.expectAvailableCallbackAndGetNetwork();
- // Check that the network is metered.
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- false /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- mTestNetworkCallback.drainAndWaitForIdle();
-
- // Before Android T, DNS queries over private DNS should be but are not restricted by Power
- // Saver or Data Saver. The issue is fixed in mainline update and apps can no longer request
- // DNS queries when its network is restricted by Power Saver. The fix takes effect backwards
- // starting from Android T. But for Data Saver, the fix is not backward compatible since
- // there are some platform changes involved. It is only available on devices that a specific
- // trunk flag is enabled.
- //
- // This test can not only verify that the network traffic from apps is blocked at the right
- // time, but also verify whether it is correctly blocked at the DNS stage, or at a later
- // socket connection stage.
- if (SdkLevel.isAtLeastT()) {
- // Enable private DNS
- mCtsNetUtils = new CtsNetUtils(mContext);
- mCtsNetUtils.storePrivateDnsSetting();
- mCtsNetUtils.setPrivateDnsStrictMode(GOOGLE_PRIVATE_DNS_SERVER);
- mCtsNetUtils.awaitPrivateDnsSetting(
- "NetworkCallbackTest wait private DNS setting timeout", mNetwork,
- GOOGLE_PRIVATE_DNS_SERVER, true);
- }
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setRestrictBackground(false);
- setBatterySaverMode(false);
- unregisterNetworkCallback();
- stopApp();
-
- if (SdkLevel.isAtLeastT() && (mCtsNetUtils != null)) {
- mCtsNetUtils.restorePrivateDnsSetting();
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testOnBlockedStatusChanged_dataSaver() throws Exception {
- try {
- // Enable restrict background
- setRestrictBackground(true);
- // TODO: Verify expectedUnavailableError when aconfig support mainline.
- // (see go/aconfig-in-mainline-problems)
- assertBackgroundNetworkAccess(false);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
-
- // Add to whitelist
- addRestrictBackgroundWhitelist(mUid);
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
-
- // Remove from whitelist
- removeRestrictBackgroundWhitelist(mUid);
- // TODO: Verify expectedUnavailableError when aconfig support mainline.
- assertBackgroundNetworkAccess(false);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
-
- // Disable restrict background, should not trigger callback
- setRestrictBackground(false);
- assertBackgroundNetworkAccess(true);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testOnBlockedStatusChanged_powerSaver() throws Exception {
- try {
- // Enable Power Saver
- setBatterySaverMode(true);
- if (SdkLevel.isAtLeastT()) {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(false, "java.net.UnknownHostException");
- } else {
- assertBackgroundNetworkAccess(false);
- }
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- // Enable Power Saver
- setBatterySaverMode(true);
- if (SdkLevel.isAtLeastT()) {
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkAccess(false, "java.net.UnknownHostException");
- } else {
- assertBackgroundNetworkAccess(false);
- }
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
-
- // Disable Power Saver
- setBatterySaverMode(false);
- assertBackgroundNetworkAccess(true);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- @Test
- public void testOnBlockedStatusChanged_default() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
-
- try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- assertNetworkAccess(false, null);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- launchActivity();
- assertTopState();
- assertNetworkAccess(true, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, true /* metered */);
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, true /* metered */);
-
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
-
- // Set to non-metered network
- mMeterednessConfiguration.configureNetworkMeteredness(false);
- mTestNetworkCallback.expectCapabilitiesCallbackEventually(mNetwork,
- true /* hasCapability */, NET_CAPABILITY_NOT_METERED);
- try {
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- assertNetworkAccess(false, null);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
-
- launchActivity();
- assertTopState();
- assertNetworkAccess(true, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, false);
- assertNetworkAccessBlockedByBpf(false, mUid, false /* metered */);
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkAccess(false, null);
- mTestNetworkCallback.expectBlockedStatusCallbackEventually(mNetwork, true);
- assertNetworkAccessBlockedByBpf(true, mUid, false /* metered */);
- } finally {
- mMeterednessConfiguration.resetNetworkMeteredness();
- }
- }
-
- // TODO: 1. test against VPN lockdown.
- // 2. test against multiple networks.
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
deleted file mode 100644
index 6c5f2ff..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyManagerTest.java
+++ /dev/null
@@ -1,276 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
-import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
-import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING;
-import static android.os.Process.SYSTEM_UID;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertIsUidRestrictedOnMeteredNetworks;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.assertNetworkingBlockedStatusForUid;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isUidNetworkingBlocked;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.netpolicy.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.netpolicy.hostside.Property.DATA_SAVER_MODE;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeTrue;
-
-import android.os.SystemClock;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class NetworkPolicyManagerTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final boolean METERED = true;
- private static final boolean NON_METERED = false;
-
- @Before
- public void setUp() throws Exception {
- super.setUp();
-
- registerBroadcastReceiver();
-
- removeRestrictBackgroundWhitelist(mUid);
- removeRestrictBackgroundBlacklist(mUid);
- assertRestrictBackgroundChangedReceived(0);
-
- // Initial state
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setRestrictedNetworkingMode(false);
- }
-
- @After
- public void tearDown() throws Exception {
- super.tearDown();
-
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setRestrictedNetworkingMode(false);
- unregisterNetworkCallback();
- stopApp();
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of non-metered network and uid not matched by any rule.
- // If mUid is not blocked by data saver mode or power saver mode, no matter the network is
- // metered or non-metered, mUid shouldn't be blocked.
- assertFalse(isUidNetworkingBlocked(mUid, METERED)); // Match NTWK_ALLOWED_DEFAULT
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- }
-
- @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the case of uid is system uid.
- // SYSTEM_UID will never be blocked.
- assertFalse(isUidNetworkingBlocked(SYSTEM_UID, METERED)); // Match NTWK_ALLOWED_SYSTEM
- assertFalse(isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
- try {
- setRestrictBackground(true);
- setBatterySaverMode(true);
- setRestrictedNetworkingMode(true);
- assertNetworkingBlockedStatusForUid(SYSTEM_UID, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_SYSTEM
- assertFalse(
- isUidNetworkingBlocked(SYSTEM_UID, NON_METERED)); // Match NTWK_ALLOWED_SYSTEM
- } finally {
- setRestrictBackground(false);
- setBatterySaverMode(false);
- setRestrictedNetworkingMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of non-metered network, uid is matched by restrict background blacklist,
- // uid is matched by restrict background whitelist, app is in the foreground with restrict
- // background enabled and the app is in the background with restrict background enabled.
- try {
- // Enable restrict background and mUid will be blocked because it's not in the
- // foreground.
- setRestrictBackground(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
-
- // Although restrict background is enabled and mUid is in the background, but mUid will
- // not be blocked if network is non-metered.
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
-
- // Add mUid into the restrict background blacklist.
- addRestrictBackgroundBlacklist(mUid);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_DENYLIST
-
- // Although mUid is in the restrict background blacklist, but mUid won't be blocked if
- // the network is non-metered.
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removeRestrictBackgroundBlacklist(mUid);
-
- // Add mUid into the restrict background whitelist.
- addRestrictBackgroundWhitelist(mUid);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_ALLOWLIST
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removeRestrictBackgroundWhitelist(mUid);
-
- // Make TEST_APP2_PKG go to foreground and mUid will be allowed temporarily.
- launchActivity();
- assertTopState();
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_TMP_ALLOWLIST
-
- // Back to background.
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_BG_RESTRICT
- } finally {
- setRestrictBackground(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of restricted networking mode enabled.
- try {
- // All apps should be blocked if restricted networking mode is enabled except for those
- // apps who have CONNECTIVITY_USE_RESTRICTED_NETWORKS permission.
- // This test won't test if an app who has CONNECTIVITY_USE_RESTRICTED_NETWORKS will not
- // be blocked because CONNECTIVITY_USE_RESTRICTED_NETWORKS is a signature/privileged
- // permission that CTS cannot acquire. Also it's not good for this test to use those
- // privileged apps which have CONNECTIVITY_USE_RESTRICTED_NETWORKS to test because there
- // is no guarantee that those apps won't remove this permission someday, and if it
- // happens, then this test will fail.
- setRestrictedNetworkingMode(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_RESTRICTED_MODE
- assertTrue(isUidNetworkingBlocked(mUid,
- NON_METERED)); // Match NTWK_BLOCKED_RESTRICTED_MODE
- } finally {
- setRestrictedNetworkingMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({BATTERY_SAVER_MODE})
- @Test
- public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
- // Refer to NetworkPolicyManagerService#isUidNetworkingBlockedInternal(), this test is to
- // test the cases of power saver mode enabled, uid in the power saver mode whitelist and
- // uid in the power saver mode whitelist with non-metered network.
- try {
- // mUid should be blocked if power saver mode is enabled.
- setBatterySaverMode(true);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- true /* expectedResult */); // Match NTWK_BLOCKED_POWER
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_BLOCKED_POWER
-
- // Add TEST_APP2_PKG into power saver mode whitelist, its uid rule is RULE_ALLOW_ALL and
- // it shouldn't be blocked.
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- assertFalse(
- isUidNetworkingBlocked(mUid, NON_METERED)); // Match NTWK_ALLOWED_NON_METERED
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- } finally {
- setBatterySaverMode(false);
- assertNetworkingBlockedStatusForUid(mUid, METERED,
- false /* expectedResult */); // Match NTWK_ALLOWED_DEFAULT
- }
- }
-
- @RequiredProperties({DATA_SAVER_MODE})
- @Test
- public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
- try {
- // isUidRestrictedOnMeteredNetworks() will only return true when restrict background is
- // enabled and mUid is not in the restrict background whitelist and TEST_APP2_PKG is not
- // in the foreground. For other cases, it will return false.
- setRestrictBackground(true);
- assertIsUidRestrictedOnMeteredNetworks(mUid, true /* expectedResult */);
-
- // Make TEST_APP2_PKG go to foreground and isUidRestrictedOnMeteredNetworks() will
- // return false.
- launchActivity();
- assertTopState();
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- // Back to background.
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
-
- // Add mUid into restrict background whitelist and isUidRestrictedOnMeteredNetworks()
- // will return false.
- addRestrictBackgroundWhitelist(mUid);
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- removeRestrictBackgroundWhitelist(mUid);
- } finally {
- // Restrict background is disabled and isUidRestrictedOnMeteredNetworks() will return
- // false.
- setRestrictBackground(false);
- assertIsUidRestrictedOnMeteredNetworks(mUid, false /* expectedResult */);
- }
- }
-
- @Test
- public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
- assumeTrue("Feature not enabled", isNetworkBlockedForTopSleepingAndAbove());
-
- try {
- assertProcessStateBelow(PROCESS_STATE_LAST_ACTIVITY);
- SystemClock.sleep(mProcessStateTransitionShortDelayMs);
- assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
-
- launchActivity();
- assertTopState();
- assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
-
- finishActivity();
- assertProcessStateBelow(PROCESS_STATE_TOP_SLEEPING);
- SystemClock.sleep(mProcessStateTransitionLongDelayMs);
- assertNetworkingBlockedStatusForUid(mUid, METERED, true /* expectedResult */);
- assertTrue(isUidNetworkingBlocked(mUid, NON_METERED));
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertNetworkingBlockedStatusForUid(mUid, METERED, false /* expectedResult */);
- assertFalse(isUidNetworkingBlocked(mUid, NON_METERED));
- } finally {
- removePowerSaveModeWhitelist(TEST_APP2_PKG);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
deleted file mode 100644
index 0207b00..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestRunner.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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 com.android.cts.netpolicy.hostside;
-
-import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
-
-import org.junit.rules.RunRules;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.Statement;
-
-import java.util.List;
-
-/**
- * Custom runner to allow dumping logs after a test failure before the @After methods get to run.
- */
-public class NetworkPolicyTestRunner extends AndroidJUnit4ClassRunner {
- private TestRule mDumpOnFailureRule = new DumpOnFailureRule();
-
- public NetworkPolicyTestRunner(Class<?> klass) throws InitializationError {
- super(klass);
- }
-
- @Override
- public Statement methodInvoker(FrameworkMethod method, Object test) {
- return new RunRules(super.methodInvoker(method, test), List.of(mDumpOnFailureRule),
- describeChild(method));
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
deleted file mode 100644
index 26a88f2..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/NetworkPolicyTestUtils.java
+++ /dev/null
@@ -1,486 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
-import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
-import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
-import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_METERED;
-import static android.net.wifi.WifiConfiguration.METERED_OVERRIDE_NONE;
-
-import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.location.LocationManager;
-import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkPolicyManager;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.net.wifi.WifiManager.ActionListener;
-import android.os.PersistableBundle;
-import android.os.Process;
-import android.os.UserHandle;
-import android.telephony.CarrierConfigManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.data.ApnSetting;
-import android.util.Log;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
-
-import com.android.compatibility.common.util.AppStandbyUtils;
-import com.android.compatibility.common.util.BatteryUtils;
-import com.android.compatibility.common.util.PollingCheck;
-import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class NetworkPolicyTestUtils {
-
- // android.telephony.CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS
- // TODO: Expose it as a @TestApi instead of copying the constant
- private static final String KEY_CARRIER_METERED_APN_TYPES_STRINGS =
- "carrier_metered_apn_types_strings";
-
- private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
-
- private static ConnectivityManager mCm;
- private static WifiManager mWm;
- private static CarrierConfigManager mCarrierConfigManager;
- private static NetworkPolicyManager sNpm;
-
- private static Boolean mBatterySaverSupported;
- private static Boolean mDataSaverSupported;
- private static Boolean mDozeModeSupported;
- private static Boolean mAppStandbySupported;
-
- private NetworkPolicyTestUtils() {}
-
- public static boolean isBatterySaverSupported() {
- if (mBatterySaverSupported == null) {
- mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
- }
- return mBatterySaverSupported;
- }
-
- private static boolean isWear() {
- return getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
- }
-
- /**
- * As per CDD requirements, if the device doesn't support data saver mode then
- * ConnectivityManager.getRestrictBackgroundStatus() will always return
- * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
- * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
- * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
- */
- public static boolean isDataSaverSupported() {
- if (isWear()) {
- return false;
- }
- if (mDataSaverSupported == null) {
- setRestrictBackgroundInternal(false);
- assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
- try {
- setRestrictBackgroundInternal(true);
- mDataSaverSupported = !isMyRestrictBackgroundStatus(
- RESTRICT_BACKGROUND_STATUS_DISABLED);
- } finally {
- setRestrictBackgroundInternal(false);
- }
- }
- return mDataSaverSupported;
- }
-
- public static boolean isDozeModeSupported() {
- if (mDozeModeSupported == null) {
- final String result = executeShellCommand("cmd deviceidle enabled deep");
- mDozeModeSupported = result.equals("1");
- }
- return mDozeModeSupported;
- }
-
- public static boolean isAppStandbySupported() {
- if (mAppStandbySupported == null) {
- mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
- }
- return mAppStandbySupported;
- }
-
- public static boolean isLowRamDevice() {
- final ActivityManager am = (ActivityManager) getContext().getSystemService(
- Context.ACTIVITY_SERVICE);
- return am.isLowRamDevice();
- }
-
- /** Forces JobScheduler to run the job if constraints are met. */
- public static void forceRunJob(String pkg, int jobId) {
- executeShellCommand("cmd jobscheduler run -f -u " + UserHandle.myUserId()
- + " " + pkg + " " + jobId);
- }
-
- public static boolean isLocationEnabled() {
- final LocationManager lm = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- return lm.isLocationEnabled();
- }
-
- public static void setLocationEnabled(boolean enabled) {
- final LocationManager lm = (LocationManager) getContext().getSystemService(
- Context.LOCATION_SERVICE);
- lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
- assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
- Log.d(TAG, "Changed location enabled state to " + enabled);
- }
-
- public static boolean isActiveNetworkMetered(boolean metered) {
- return getConnectivityManager().isActiveNetworkMetered() == metered;
- }
-
- public static boolean canChangeActiveNetworkMeteredness() {
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
- return networkCapabilities.hasTransport(TRANSPORT_WIFI)
- || networkCapabilities.hasTransport(TRANSPORT_CELLULAR);
- }
-
- /**
- * Updates the meteredness of the active network. Right now we can only change meteredness
- * of either Wifi or cellular network, so if the active network is not either of these, this
- * will throw an exception.
- *
- * @return a {@link ThrowingRunnable} object that can used to reset the meteredness change
- * made by this method.
- */
- public static ThrowingRunnable setupActiveNetworkMeteredness(boolean metered) throws Exception {
- if (isActiveNetworkMetered(metered)) {
- return null;
- }
- final NetworkCapabilities networkCapabilities = getActiveNetworkCapabilities();
- if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
- final String ssid = getWifiSsid();
- setWifiMeteredStatus(ssid, metered);
- return () -> setWifiMeteredStatus(ssid, !metered);
- } else if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
- final int subId = SubscriptionManager.getActiveDataSubscriptionId();
- setCellularMeteredStatus(subId, metered);
- return () -> setCellularMeteredStatus(subId, !metered);
- } else {
- // Right now, we don't have a way to change meteredness of networks other
- // than Wi-Fi or Cellular, so just throw an exception.
- throw new IllegalStateException("Can't change meteredness of current active network");
- }
- }
-
- private static String getWifiSsid() {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- final String ssid = getWifiManager().getConnectionInfo().getSSID();
- assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
- return ssid;
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- static NetworkCapabilities getActiveNetworkCapabilities() {
- final Network activeNetwork = getConnectivityManager().getActiveNetwork();
- assertNotNull("No active network available", activeNetwork);
- return getConnectivityManager().getNetworkCapabilities(activeNetwork);
- }
-
- private static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- final WifiConfiguration currentConfig = getWifiConfiguration(ssid);
- currentConfig.meteredOverride = metered
- ? METERED_OVERRIDE_METERED : METERED_OVERRIDE_NONE;
- BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<>();
- getWifiManager().save(currentConfig, createActionListener(
- blockingQueue, Integer.MAX_VALUE));
- Integer resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
- TimeUnit.MILLISECONDS);
- if (resultCode == null) {
- fail("Timed out waiting for meteredness to change; ssid=" + ssid
- + ", metered=" + metered);
- } else if (resultCode != Integer.MAX_VALUE) {
- fail("Error overriding the meteredness; ssid=" + ssid
- + ", metered=" + metered + ", error=" + resultCode);
- }
- final boolean success = assertActiveNetworkMetered(metered, false /* throwOnFailure */);
- if (!success) {
- Log.i(TAG, "Retry connecting to wifi; ssid=" + ssid);
- blockingQueue = new LinkedBlockingQueue<>();
- getWifiManager().connect(currentConfig, createActionListener(
- blockingQueue, Integer.MAX_VALUE));
- resultCode = blockingQueue.poll(TIMEOUT_CHANGE_METEREDNESS_MS,
- TimeUnit.MILLISECONDS);
- if (resultCode == null) {
- fail("Timed out waiting for wifi to connect; ssid=" + ssid);
- } else if (resultCode != Integer.MAX_VALUE) {
- fail("Error connecting to wifi; ssid=" + ssid
- + ", error=" + resultCode);
- }
- assertActiveNetworkMetered(metered, true /* throwOnFailure */);
- }
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- private static WifiConfiguration getWifiConfiguration(String ssid) {
- final List<String> ssids = new ArrayList<>();
- for (WifiConfiguration config : getWifiManager().getConfiguredNetworks()) {
- if (config.SSID.equals(ssid)) {
- return config;
- }
- ssids.add(config.SSID);
- }
- fail("Couldn't find the wifi config; ssid=" + ssid
- + ", all=" + Arrays.toString(ssids.toArray()));
- return null;
- }
-
- private static ActionListener createActionListener(BlockingQueue<Integer> blockingQueue,
- int successCode) {
- return new ActionListener() {
- @Override
- public void onSuccess() {
- blockingQueue.offer(successCode);
- }
-
- @Override
- public void onFailure(int reason) {
- blockingQueue.offer(reason);
- }
- };
- }
-
- private static void setCellularMeteredStatus(int subId, boolean metered) throws Exception {
- final PersistableBundle bundle = new PersistableBundle();
- bundle.putStringArray(KEY_CARRIER_METERED_APN_TYPES_STRINGS,
- new String[] {ApnSetting.TYPE_MMS_STRING});
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(getCarrierConfigManager(),
- (cm) -> cm.overrideConfig(subId, metered ? null : bundle));
- assertActiveNetworkMetered(metered, true /* throwOnFailure */);
- }
-
- private static boolean assertActiveNetworkMetered(boolean expectedMeteredStatus,
- boolean throwOnFailure) throws Exception {
- final CountDownLatch latch = new CountDownLatch(1);
- final NetworkCallback networkCallback = new NetworkCallback() {
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
- final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
- if (metered == expectedMeteredStatus) {
- latch.countDown();
- }
- }
- };
- // Registering a callback here guarantees onCapabilitiesChanged is called immediately
- // with the current setting. Therefore, if the setting has already been changed,
- // this method will return right away, and if not it will wait for the setting to change.
- getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
- try {
- if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
- final String errorMsg = "Timed out waiting for active network metered status "
- + "to change to " + expectedMeteredStatus + "; network = "
- + getConnectivityManager().getActiveNetwork();
- if (throwOnFailure) {
- fail(errorMsg);
- }
- Log.w(TAG, errorMsg);
- return false;
- }
- return true;
- } finally {
- getConnectivityManager().unregisterNetworkCallback(networkCallback);
- }
- }
-
- public static void setRestrictBackground(boolean enabled) {
- if (!isDataSaverSupported()) {
- return;
- }
- setRestrictBackgroundInternal(enabled);
- }
-
- static void setRestrictBackgroundInternal(boolean enabled) {
- executeShellCommand("cmd netpolicy set restrict-background " + enabled);
- final String output = executeShellCommand("cmd netpolicy get restrict-background");
- final String expectedSuffix = enabled ? "enabled" : "disabled";
- assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
- output.endsWith(expectedSuffix));
- }
-
- public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
- final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
- if (expectedStatus != actualStatus) {
- Log.d(TAG, "MyRestrictBackgroundStatus: "
- + "Expected: " + restrictBackgroundValueToString(expectedStatus)
- + "; Actual: " + restrictBackgroundValueToString(actualStatus));
- return false;
- }
- return true;
- }
-
- // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
- private static String unquoteSSID(String ssid) {
- // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
- // Otherwise it's guaranteed not to start with a quote.
- if (ssid.charAt(0) == '"') {
- return ssid.substring(1, ssid.length() - 1);
- } else {
- return ssid;
- }
- }
-
- public static String restrictBackgroundValueToString(int status) {
- switch (status) {
- case RESTRICT_BACKGROUND_STATUS_DISABLED:
- return "DISABLED";
- case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
- return "WHITELISTED";
- case RESTRICT_BACKGROUND_STATUS_ENABLED:
- return "ENABLED";
- default:
- return "UNKNOWN_STATUS_" + status;
- }
- }
-
- public static void clearSnoozeTimestamps() {
- executeShellCommand("dumpsys netpolicy --unsnooze");
- }
-
- public static String executeShellCommand(String command) {
- final String result = runShellCommandOrThrow(command).trim();
- Log.d(TAG, "Output of '" + command + "': '" + result + "'");
- return result;
- }
-
- public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
- final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
- assertEquals(restrictBackgroundValueToString(expectedStatus),
- restrictBackgroundValueToString(actualStatus));
- }
-
- public static ConnectivityManager getConnectivityManager() {
- if (mCm == null) {
- mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
- }
- return mCm;
- }
-
- public static WifiManager getWifiManager() {
- if (mWm == null) {
- mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
- }
- return mWm;
- }
-
- public static CarrierConfigManager getCarrierConfigManager() {
- if (mCarrierConfigManager == null) {
- mCarrierConfigManager = (CarrierConfigManager) getContext().getSystemService(
- Context.CARRIER_CONFIG_SERVICE);
- }
- return mCarrierConfigManager;
- }
-
- public static NetworkPolicyManager getNetworkPolicyManager() {
- if (sNpm == null) {
- sNpm = getContext().getSystemService(NetworkPolicyManager.class);
- }
- return sNpm;
- }
-
- public static Context getContext() {
- return getInstrumentation().getContext();
- }
-
- public static Instrumentation getInstrumentation() {
- return InstrumentationRegistry.getInstrumentation();
- }
-
- public static UiDevice getUiDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
- // When power saver mode or restrict background enabled or adding any white/black list into
- // those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
- // this function and using PollingCheck to try to make sure the uid has updated and reduce the
- // flaky rate.
- public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
- boolean expectedResult) {
- final String errMsg = String.format("Unexpected result from isUidNetworkingBlocked; "
- + "uid= " + uid + ", metered=" + metered + ", expected=" + expectedResult);
- PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)),
- errMsg);
- }
-
- public static void assertIsUidRestrictedOnMeteredNetworks(int uid, boolean expectedResult) {
- final String errMsg = String.format(
- "Unexpected result from isUidRestrictedOnMeteredNetworks; "
- + "uid= " + uid + ", expected=" + expectedResult);
- PollingCheck.waitFor(() -> (expectedResult == isUidRestrictedOnMeteredNetworks(uid)),
- errMsg);
- }
-
- public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return getNetworkPolicyManager().isUidNetworkingBlocked(uid, meteredNetwork);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-
- public static boolean isUidRestrictedOnMeteredNetworks(int uid) {
- final UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
- try {
- uiAutomation.adoptShellPermissionIdentity();
- return getNetworkPolicyManager().isUidRestrictedOnMeteredNetworks(uid);
- } finally {
- uiAutomation.dropShellPermissionIdentity();
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
deleted file mode 100644
index a03833f..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/Property.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
-import static com.android.cts.netpolicy.hostside.NetworkPolicyTestUtils.isLowRamDevice;
-
-public enum Property {
- BATTERY_SAVER_MODE(1 << 0) {
- public boolean isSupported() { return isBatterySaverSupported(); }
- },
-
- DATA_SAVER_MODE(1 << 1) {
- public boolean isSupported() { return isDataSaverSupported(); }
- },
-
- NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
- public boolean isSupported() { return !isDataSaverSupported(); }
- },
-
- DOZE_MODE(1 << 2) {
- public boolean isSupported() { return isDozeModeSupported(); }
- },
-
- APP_STANDBY_MODE(1 << 3) {
- public boolean isSupported() { return isAppStandbySupported(); }
- },
-
- NOT_LOW_RAM_DEVICE(1 << 4) {
- public boolean isSupported() { return !isLowRamDevice(); }
- },
-
- METERED_NETWORK(1 << 5) {
- public boolean isSupported() {
- return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
- }
- },
-
- NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
- public boolean isSupported() {
- return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
- }
- };
-
- private int mValue;
-
- Property(int value) { mValue = value; }
-
- public int getValue() { return mValue; }
-
- abstract boolean isSupported();
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
deleted file mode 100644
index 799a513..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredProperties.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static java.lang.annotation.ElementType.METHOD;
-import static java.lang.annotation.ElementType.TYPE;
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import java.lang.annotation.Inherited;
-import java.lang.annotation.Retention;
-import java.lang.annotation.Target;
-
-@Retention(RUNTIME)
-@Target({METHOD, TYPE})
-@Inherited
-public @interface RequiredProperties {
- Property[] value();
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
deleted file mode 100644
index 5dea67c..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RequiredPropertiesRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy.hostside;
-
-import static com.android.cts.netpolicy.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
-
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BeforeAfterRule;
-
-import org.junit.Assume;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.ArrayList;
-import java.util.Collections;
-
-public class RequiredPropertiesRule extends BeforeAfterRule {
-
- private static ArraySet<Property> mRequiredProperties;
-
- @Override
- public void onBefore(Statement base, Description description) {
- mRequiredProperties = getAllRequiredProperties(description);
-
- final String testName = description.getClassName() + "#" + description.getMethodName();
- assertTestIsValid(testName, mRequiredProperties);
- Log.i(TAG, "Running test " + testName + " with required properties: "
- + propertiesToString(mRequiredProperties));
- }
-
- private ArraySet<Property> getAllRequiredProperties(Description description) {
- final ArraySet<Property> allRequiredProperties = new ArraySet<>();
- RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
- if (requiredProperties != null) {
- Collections.addAll(allRequiredProperties, requiredProperties.value());
- }
-
- for (Class<?> clazz = description.getTestClass();
- clazz != null; clazz = clazz.getSuperclass()) {
- requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
- if (requiredProperties == null) {
- continue;
- }
- for (Property requiredProperty : requiredProperties.value()) {
- for (Property p : Property.values()) {
- if (p.getValue() == ~requiredProperty.getValue()
- && allRequiredProperties.contains(p)) {
- continue;
- }
- }
- allRequiredProperties.add(requiredProperty);
- }
- }
- return allRequiredProperties;
- }
-
- private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
- if (requiredProperies == null) {
- return;
- }
- final ArrayList<Property> unsupportedProperties = new ArrayList<>();
- for (Property property : requiredProperies) {
- if (!property.isSupported()) {
- unsupportedProperties.add(property);
- }
- }
- Assume.assumeTrue("Unsupported properties: "
- + propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
- }
-
- public static ArraySet<Property> getRequiredProperties() {
- return mRequiredProperties;
- }
-
- private static String propertiesToString(Iterable<Property> properties) {
- return "[" + TextUtils.join(",", properties) + "]";
- }
-}
diff --git a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java b/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
deleted file mode 100644
index f183f4e..0000000
--- a/tests/cts/hostside-network-policy/app/src/com/android/cts/netpolicy/hostside/RestrictedModeTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public final class RestrictedModeTest extends AbstractRestrictBackgroundNetworkTestCase {
- @Before
- public void setUp() throws Exception {
- super.setUp();
- setRestrictedNetworkingMode(false);
- }
-
- @After
- public void tearDown() throws Exception {
- setRestrictedNetworkingMode(false);
- super.tearDown();
- }
-
- @Test
- public void testNetworkAccess() throws Exception {
- // go to foreground state and enable restricted mode
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- setRestrictedNetworkingMode(true);
- assertTopNetworkAccess(false);
-
- // go to background state
- finishActivity();
- assertBackgroundNetworkAccess(false);
-
- // disable restricted mode and assert network access in foreground and background states
- setRestrictedNetworkingMode(false);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- assertTopNetworkAccess(true);
-
- // go to background state
- finishActivity();
- assertBackgroundNetworkAccess(true);
- }
-
- @Test
- public void testNetworkAccess_withBatterySaver() throws Exception {
- setBatterySaverMode(true);
- try {
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertBackgroundNetworkAccess(true);
-
- setRestrictedNetworkingMode(true);
- // App would be denied network access since Restricted mode is on.
- assertBackgroundNetworkAccess(false);
- setRestrictedNetworkingMode(false);
- // Given that Restricted mode is turned off, app should be able to access network again.
- assertBackgroundNetworkAccess(true);
- } finally {
- setBatterySaverMode(false);
- }
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/Android.bp b/tests/cts/hostside-network-policy/app2/Android.bp
deleted file mode 100644
index 6ef0b06..0000000
--- a/tests/cts/hostside-network-policy/app2/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-//
-// 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_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
- name: "CtsHostsideNetworkPolicyTestsApp2",
- defaults: ["cts_support_defaults"],
- platform_apis: true,
- static_libs: [
- "androidx.annotation_annotation",
- "CtsHostsideNetworkPolicyTestsAidl",
- "modules-utils-build",
- ],
- srcs: ["src/**/*.java"],
- // Tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
- "sts",
- ],
- certificate: ":cts-netpolicy-app",
-}
diff --git a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml b/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
deleted file mode 100644
index 668f2da..0000000
--- a/tests/cts/hostside-network-policy/app2/AndroidManifest.xml
+++ /dev/null
@@ -1,77 +0,0 @@
-<?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="com.android.cts.netpolicy.hostside.app2">
-
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
- <uses-permission android:name="android.permission.INTERNET"/>
- <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
- <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
-
- <!--
- This application is used to listen to RESTRICT_BACKGROUND_CHANGED intents and store
- them in a shared preferences which is then read by the test app. These broadcasts are
- handled by 2 listeners, one defined the manifest and another dynamically registered by
- a service.
-
- The manifest-defined listener also handles ordered broadcasts used to share data with the
- test app.
-
- This application also provides a service, RemoteSocketFactoryService, that the test app can
- use to open sockets to remote hosts as a different user ID.
- -->
- <application android:usesCleartextTraffic="true"
- android:testOnly="true"
- android:debuggable="true">
-
- <activity android:name=".MyActivity"
- android:exported="true"/>
- <service android:name=".MyService"
- android:exported="true"/>
- <service android:name=".MyForegroundService"
- android:foregroundServiceType="specialUse"
- android:exported="true">
- <property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
- android:value="Connectivity" />
- </service>
- <receiver android:name=".MyBroadcastReceiver"
- android:exported="true">
- <intent-filter>
- <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_COUNTERS"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.CHECK_NETWORK"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.SEND_NOTIFICATION"/>
- <action android:name="com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST"/>
- </intent-filter>
- </receiver>
- <service android:name=".MyJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
- </application>
-
- <!--
- Adding this to make sure that receiving the broadcast is not restricted by
- package visibility restrictions.
- -->
- <queries>
- <package android:name="android" />
- </queries>
-
-</manifest>
diff --git a/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png b/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
deleted file mode 100644
index 6ae570b..0000000
--- a/tests/cts/hostside-network-policy/app2/res/drawable/ic_notification.png
+++ /dev/null
Binary files differ
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
deleted file mode 100644
index 1719f9b..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/Common.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_OTHER;
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_CAPABILITIES;
-import static com.android.cts.netpolicy.hostside.INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.INetworkStateObserver;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
-
-public final class Common {
-
- static final String TAG = "CtsNetApp2";
-
- // Constants below must match values defined on app's
- // AbstractRestrictBackgroundNetworkTestCase.java
- static final String MANIFEST_RECEIVER = "ManifestReceiver";
- static final String DYNAMIC_RECEIVER = "DynamicReceiver";
-
- static final String ACTION_RECEIVER_READY =
- "com.android.cts.netpolicy.hostside.app2.action.RECEIVER_READY";
- static final String ACTION_FINISH_ACTIVITY =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_ACTIVITY";
- static final String ACTION_FINISH_JOB =
- "com.android.cts.netpolicy.hostside.app2.action.FINISH_JOB";
- static final String ACTION_SHOW_TOAST =
- "com.android.cts.netpolicy.hostside.app2.action.SHOW_TOAST";
- // Copied from com.android.server.net.NetworkPolicyManagerService class
- static final String ACTION_SNOOZE_WARNING =
- "com.android.server.net.action.SNOOZE_WARNING";
-
- private static final String DEFAULT_TEST_URL =
- "https://connectivitycheck.android.com/generate_204";
-
- static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
- static final String NOTIFICATION_TYPE_DELETE = "DELETE";
- static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
- static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION = "ACTION";
- static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
- static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
-
- static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
- static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
- static final String KEY_CUSTOM_URL = TEST_PKG + ".custom_url";
-
- static final int TYPE_COMPONENT_ACTIVTY = 0;
- static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
- static final int TYPE_COMPONENT_EXPEDITED_JOB = 2;
- private static final int NETWORK_TIMEOUT_MS = (int) TimeUnit.SECONDS.toMillis(10);
-
- static int getUid(Context context) {
- final String packageName = context.getPackageName();
- try {
- return context.getPackageManager().getPackageUid(packageName, 0);
- } catch (NameNotFoundException e) {
- throw new IllegalStateException("Could not get UID for " + packageName, e);
- }
- }
-
- private static NetworkCheckResult createNetworkCheckResult(boolean connected, String details,
- NetworkInfo networkInfo) {
- final NetworkCheckResult checkResult = new NetworkCheckResult();
- checkResult.connected = connected;
- checkResult.details = details;
- checkResult.networkInfo = networkInfo;
- return checkResult;
- }
-
- private static boolean validateComponentState(Context context, int componentType,
- INetworkStateObserver observer) throws RemoteException {
- final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- switch (componentType) {
- case TYPE_COMPONENT_ACTIVTY: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_TOP) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
- createNetworkCheckResult(false, "Unexpected procstate: " + procState,
- null));
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_FOREGROUND_SERVICE: {
- final int procState = activityManager.getUidProcessState(Process.myUid());
- if (procState != ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_PROC_STATE,
- createNetworkCheckResult(false, "Unexpected procstate: " + procState,
- null));
- return false;
- }
- return true;
- }
- case TYPE_COMPONENT_EXPEDITED_JOB: {
- final int capabilities = activityManager.getUidProcessCapabilities(Process.myUid());
- if ((capabilities
- & ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) == 0) {
- observer.onNetworkStateChecked(RESULT_ERROR_UNEXPECTED_CAPABILITIES,
- createNetworkCheckResult(false,
- "Unexpected capabilities: " + capabilities, null));
- return false;
- }
- return true;
- }
- default: {
- observer.onNetworkStateChecked(RESULT_ERROR_OTHER,
- createNetworkCheckResult(false, "Unknown component type: " + componentType,
- null));
- return false;
- }
- }
- }
-
- static void notifyNetworkStateObserver(Context context, Intent intent, int componentType) {
- if (intent == null) {
- return;
- }
- final Bundle extras = intent.getExtras();
- notifyNetworkStateObserver(context, extras, componentType);
- }
-
- static void notifyNetworkStateObserver(Context context, Bundle extras, int componentType) {
- if (extras == null) {
- return;
- }
- final INetworkStateObserver observer = INetworkStateObserver.Stub.asInterface(
- extras.getBinder(KEY_NETWORK_STATE_OBSERVER));
- if (observer != null) {
- final String customUrl = extras.getString(KEY_CUSTOM_URL);
- try {
- final boolean skipValidation = extras.getBoolean(KEY_SKIP_VALIDATION_CHECKS);
- if (!skipValidation && !validateComponentState(context, componentType, observer)) {
- return;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while informing the validation result: " + e);
- }
- AsyncTask.execute(() -> {
- try {
- observer.onNetworkStateChecked(
- INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED,
- checkNetworkStatus(context, customUrl));
- } catch (RemoteException e) {
- Log.e(TAG, "Error occurred while notifying the observer: " + e);
- }
- });
- }
- }
-
- /**
- * Checks whether the network is available by attempting a connection to the given address
- * and returns a {@link NetworkCheckResult} object containing all the relevant details for
- * debugging. Uses a default address if the given address is {@code null}.
- *
- * <p>
- * The returned object has the following fields:
- *
- * <ul>
- * <li>{@code connected}: whether or not the connection was successful.
- * <li>{@code networkInfo}: the {@link NetworkInfo} describing the current active network as
- * visible to this app.
- * <li>{@code details}: A human readable string giving useful information about the success or
- * failure.
- * </ul>
- */
- static NetworkCheckResult checkNetworkStatus(Context context, String customUrl) {
- final String address = (customUrl == null) ? DEFAULT_TEST_URL : customUrl;
-
- // The current Android DNS resolver returns an UnknownHostException whenever network access
- // is blocked. This can get cached in the current process-local InetAddress cache. Clearing
- // the cache before attempting a connection ensures we never report a failure due to a
- // negative cache entry.
- InetAddress.clearDnsCache();
-
- final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
-
- final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
- Log.d(TAG, "Running checkNetworkStatus() on thread "
- + Thread.currentThread().getName() + " for UID " + getUid(context)
- + "\n\tactiveNetworkInfo: " + networkInfo + "\n\tURL: " + address);
- boolean checkStatus = false;
- String checkDetails = "N/A";
- try {
- final URL url = new URL(address);
- final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setReadTimeout(NETWORK_TIMEOUT_MS);
- conn.setConnectTimeout(NETWORK_TIMEOUT_MS / 2);
- conn.setRequestMethod("GET");
- conn.connect();
- final int response = conn.getResponseCode();
- checkStatus = true;
- checkDetails = "HTTP response for " + address + ": " + response;
- } catch (Exception e) {
- checkStatus = false;
- checkDetails = "Exception getting " + address + ": " + e;
- }
- final NetworkCheckResult result = createNetworkCheckResult(checkStatus, checkDetails,
- networkInfo);
- Log.d(TAG, "Offering: " + result);
- return result;
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
deleted file mode 100644
index d274c50..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyActivity.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_ACTIVITY;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_ACTIVTY;
-
-import android.app.Activity;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.RemoteCallback;
-import android.util.Log;
-import android.view.WindowManager;
-
-import androidx.annotation.GuardedBy;
-
-/**
- * Activity used to bring process to foreground.
- */
-public class MyActivity extends Activity {
-
- @GuardedBy("this")
- private BroadcastReceiver finishCommandReceiver = null;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.d(TAG, "MyActivity.onCreate()");
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- }
-
- @Override
- public void finish() {
- synchronized (this) {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
- finishCommandReceiver = null;
- }
- }
- super.finish();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Log.d(TAG, "MyActivity.onStart()");
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- Log.d(TAG, "MyActivity.onNewIntent()");
- setIntent(intent);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.d(TAG, "MyActivity.onResume(): " + getIntent());
- Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
- synchronized (this) {
- finishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "Finishing MyActivity");
- MyActivity.this.finish();
- }
- };
- registerReceiver(finishCommandReceiver, new IntentFilter(ACTION_FINISH_ACTIVITY),
- Context.RECEIVER_EXPORTED);
- }
- final RemoteCallback callback = getIntent().getParcelableExtra(
- Intent.EXTRA_REMOTE_CALLBACK);
- if (callback != null) {
- callback.sendResult(null);
- }
- }
-
- @Override
- protected void onDestroy() {
- Log.d(TAG, "MyActivity.onDestroy()");
- super.onDestroy();
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
deleted file mode 100644
index 27aec8c..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyBroadcastReceiver.java
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SHOW_TOAST;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.netpolicy.hostside.app2.Common.MANIFEST_RECEIVER;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_BUNDLE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_ACTION_REMOTE_INPUT;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_BUNDLE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_CONTENT;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_DELETE;
-import static com.android.cts.netpolicy.hostside.app2.Common.NOTIFICATION_TYPE_FULL_SCREEN;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-
-import android.app.Notification;
-import android.app.Notification.Action;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.RemoteInput;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.os.Bundle;
-import android.util.Log;
-import android.widget.Toast;
-
-/**
- * Receiver used to:
- * <ol>
- * <li>Count number of {@code RESTRICT_BACKGROUND_CHANGED} broadcasts received.
- * <li>Show a toast.
- * </ol>
- */
-public class MyBroadcastReceiver extends BroadcastReceiver {
-
- private final String mName;
-
- public MyBroadcastReceiver() {
- this(MANIFEST_RECEIVER);
- }
-
- MyBroadcastReceiver(String name) {
- Log.d(TAG, "Constructing MyBroadcastReceiver named " + name);
- mName = name;
- }
-
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.d(TAG, "onReceive() for " + mName + ": " + intent);
- final String action = intent.getAction();
- switch (action) {
- case ACTION_SNOOZE_WARNING:
- increaseCounter(context, action);
- break;
- case ACTION_RESTRICT_BACKGROUND_CHANGED:
- increaseCounter(context, action);
- break;
- case ACTION_RECEIVER_READY:
- final String message = mName + " is ready to rumble";
- Log.d(TAG, message);
- setResultData(message);
- break;
- case ACTION_SHOW_TOAST:
- showToast(context);
- break;
- default:
- Log.e(TAG, "received unexpected action: " + action);
- }
- }
-
- @Override
- public String toString() {
- return "[MyBroadcastReceiver: mName=" + mName + "]";
- }
-
- private void increaseCounter(Context context, String action) {
- final SharedPreferences prefs = context.getApplicationContext()
- .getSharedPreferences(mName, Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0) + 1;
- Log.d(TAG, "increaseCounter('" + action + "'): setting '" + mName + "' to " + value);
- prefs.edit().putInt(action, value).apply();
- }
-
- static int getCounter(Context context, String action, String receiverName) {
- final SharedPreferences prefs = context.getSharedPreferences(receiverName,
- Context.MODE_PRIVATE);
- final int value = prefs.getInt(action, 0);
- Log.d(TAG, "getCounter('" + action + "', '" + receiverName + "'): " + value);
- return value;
- }
-
- static String getRestrictBackgroundStatus(Context context) {
- final ConnectivityManager cm = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- final int apiStatus = cm.getRestrictBackgroundStatus();
- Log.d(TAG, "getRestrictBackgroundStatus: returning " + apiStatus);
- return String.valueOf(apiStatus);
- }
-
- /**
- * Sends a system notification containing actions with pending intents to launch the app's
- * main activitiy or service.
- */
- static void sendNotification(Context context, String channelId, int notificationId,
- String notificationType ) {
- Log.d(TAG, "sendNotification: id=" + notificationId + ", type=" + notificationType);
- final Intent serviceIntent = new Intent(context, MyService.class);
- final PendingIntent pendingIntent = PendingIntent.getService(context, 0, serviceIntent,
- PendingIntent.FLAG_MUTABLE);
- final Bundle bundle = new Bundle();
- bundle.putCharSequence("parcelable", "I am not");
-
- final Notification.Builder builder = new Notification.Builder(context, channelId)
- .setSmallIcon(R.drawable.ic_notification);
-
- Action action = null;
- switch (notificationType) {
- case NOTIFICATION_TYPE_CONTENT:
- builder
- .setContentTitle("Light, Cameras...")
- .setContentIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_DELETE:
- builder.setDeleteIntent(pendingIntent);
- break;
- case NOTIFICATION_TYPE_FULL_SCREEN:
- builder.setFullScreenIntent(pendingIntent, true);
- break;
- case NOTIFICATION_TYPE_BUNDLE:
- bundle.putParcelable("Magnum P.I. (Pending Intent)", pendingIntent);
- builder.setExtras(bundle);
- break;
- case NOTIFICATION_TYPE_ACTION:
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION", pendingIntent)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_BUNDLE:
- bundle.putParcelable("Magnum A.P.I. (Action Pending Intent)", pendingIntent);
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH BUNDLE", null)
- .addExtras(bundle)
- .build();
- builder.addAction(action);
- break;
- case NOTIFICATION_TYPE_ACTION_REMOTE_INPUT:
- bundle.putParcelable("Magnum R.I. (Remote Input)", null);
- final RemoteInput remoteInput = new RemoteInput.Builder("RI")
- .addExtras(bundle)
- .build();
- action = new Action.Builder(
- R.drawable.ic_notification, "ACTION WITH REMOTE INPUT", pendingIntent)
- .addRemoteInput(remoteInput)
- .build();
- builder.addAction(action);
- break;
- default:
- Log.e(TAG, "Unknown notification type: " + notificationType);
- return;
- }
-
- final Notification notification = builder.build();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .notify(notificationId, notification);
- }
-
- private void showToast(Context context) {
- Toast.makeText(context, "Toast from CTS test", Toast.LENGTH_SHORT).show();
- setResultData("Shown");
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
deleted file mode 100644
index 54cee3c..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyForegroundService.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TEST_PKG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_FOREGROUND_SERVICE;
-
-import android.R;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.INetworkStateObserver;
-
-/**
- * Service used to change app state to FOREGROUND_SERVICE.
- */
-public class MyForegroundService extends Service {
- private static final String NOTIFICATION_CHANNEL_ID = "cts/MyForegroundService";
- private static final int FLAG_START_FOREGROUND = 1;
- private static final int FLAG_STOP_FOREGROUND = 2;
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.v(TAG, "MyForegroundService.onStartCommand(): " + intent);
- NotificationManager notificationManager = getSystemService(NotificationManager.class);
- notificationManager.createNotificationChannel(new NotificationChannel(
- NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
- NotificationManager.IMPORTANCE_DEFAULT));
- switch (intent.getFlags()) {
- case FLAG_START_FOREGROUND:
- Log.d(TAG, "Starting foreground");
- startForeground(42, new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
- .setSmallIcon(R.drawable.ic_dialog_alert) // any icon is fine
- .build());
- Common.notifyNetworkStateObserver(this, intent, TYPE_COMPONENT_FOREGROUND_SERVICE);
- break;
- case FLAG_STOP_FOREGROUND:
- Log.d(TAG, "Stopping foreground");
- stopForeground(true);
- break;
- default:
- Log.wtf(TAG, "Invalid flag on intent " + intent);
- }
- return START_STICKY;
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
deleted file mode 100644
index eba55ed..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyJobService.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy.hostside.app2;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_FINISH_JOB;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-import static com.android.cts.netpolicy.hostside.app2.Common.TYPE_COMPONENT_EXPEDITED_JOB;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.util.Log;
-
-public class MyJobService extends JobService {
-
- private BroadcastReceiver mFinishCommandReceiver = null;
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.v(TAG, "MyJobService.onCreate()");
- }
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.v(TAG, "MyJobService.onStartJob()");
- Common.notifyNetworkStateObserver(this, params.getTransientExtras(),
- TYPE_COMPONENT_EXPEDITED_JOB);
- mFinishCommandReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- Log.v(TAG, "Finishing MyJobService");
- try {
- jobFinished(params, /*wantsReschedule=*/ false);
- } finally {
- if (mFinishCommandReceiver != null) {
- unregisterReceiver(mFinishCommandReceiver);
- mFinishCommandReceiver = null;
- }
- }
- }
- };
- registerReceiver(mFinishCommandReceiver, new IntentFilter(ACTION_FINISH_JOB),
- Context.RECEIVER_EXPORTED);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- // If this job is stopped before it had a chance to send network status via
- // INetworkStateObserver, the test will fail. It could happen either due to test timing out
- // or this app moving to a lower proc_state and losing network access.
- Log.v(TAG, "MyJobService.onStopJob()");
- if (mFinishCommandReceiver != null) {
- unregisterReceiver(mFinishCommandReceiver);
- mFinishCommandReceiver = null;
- }
- return false;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.v(TAG, "MyJobService.onDestroy()");
- }
-}
diff --git a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java b/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
deleted file mode 100644
index 71bcead..0000000
--- a/tests/cts/hostside-network-policy/app2/src/com/android/cts/netpolicy/hostside/app2/MyService.java
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy.hostside.app2;
-
-import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_RECEIVER_READY;
-import static com.android.cts.netpolicy.hostside.app2.Common.ACTION_SNOOZE_WARNING;
-import static com.android.cts.netpolicy.hostside.app2.Common.DYNAMIC_RECEIVER;
-import static com.android.cts.netpolicy.hostside.app2.Common.TAG;
-
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.cts.netpolicy.hostside.IMyService;
-import com.android.cts.netpolicy.hostside.INetworkCallback;
-import com.android.cts.netpolicy.hostside.NetworkCheckResult;
-import com.android.modules.utils.build.SdkLevel;
-
-/**
- * Service used to dynamically register a broadcast receiver.
- */
-public class MyService extends Service {
- private static final String NOTIFICATION_CHANNEL_ID = "MyService";
-
- ConnectivityManager mCm;
-
- private MyBroadcastReceiver mReceiver;
- private ConnectivityManager.NetworkCallback mNetworkCallback;
-
- // TODO: move MyBroadcast static functions here - they were kept there to make git diff easier.
-
- private IMyService.Stub mBinder = new IMyService.Stub() {
- @Override
- public void registerBroadcastReceiver() {
- if (mReceiver != null) {
- Log.d(TAG, "receiver already registered: " + mReceiver);
- return;
- }
- final Context context = getApplicationContext();
- final int flags = SdkLevel.isAtLeastT() ? RECEIVER_EXPORTED : 0;
- mReceiver = new MyBroadcastReceiver(DYNAMIC_RECEIVER);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_RECEIVER_READY), flags);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED), flags);
- context.registerReceiver(mReceiver,
- new IntentFilter(ACTION_SNOOZE_WARNING), flags);
- Log.d(TAG, "receiver registered");
- }
-
- @Override
- public int getCounters(String receiverName, String action) {
- return MyBroadcastReceiver.getCounter(getApplicationContext(), action, receiverName);
- }
-
- @Override
- public NetworkCheckResult checkNetworkStatus(String customUrl) {
- return Common.checkNetworkStatus(getApplicationContext(), customUrl);
- }
-
- @Override
- public String getRestrictBackgroundStatus() {
- return MyBroadcastReceiver.getRestrictBackgroundStatus(getApplicationContext());
- }
-
- @Override
- public void sendNotification(int notificationId, String notificationType) {
- MyBroadcastReceiver.sendNotification(getApplicationContext(), NOTIFICATION_CHANNEL_ID,
- notificationId, notificationType);
- }
-
- @Override
- public void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb) {
- if (mNetworkCallback != null) {
- Log.d(TAG, "unregister previous network callback: " + mNetworkCallback);
- unregisterNetworkCallback();
- }
- Log.d(TAG, "registering network callback for " + request);
-
- mNetworkCallback = new ConnectivityManager.NetworkCallback() {
- @Override
- public void onBlockedStatusChanged(Network network, boolean blocked) {
- try {
- cb.onBlockedStatusChanged(network, blocked);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onBlockedStatusChanged: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onAvailable(Network network) {
- try {
- cb.onAvailable(network);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onAvailable: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onLost(Network network) {
- try {
- cb.onLost(network);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onLost: " + e);
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void onCapabilitiesChanged(Network network, NetworkCapabilities cap) {
- try {
- cb.onCapabilitiesChanged(network, cap);
- } catch (RemoteException e) {
- Log.d(TAG, "Cannot send onCapabilitiesChanged: " + e);
- unregisterNetworkCallback();
- }
- }
- };
- mCm.registerNetworkCallback(request, mNetworkCallback);
- try {
- cb.asBinder().linkToDeath(() -> unregisterNetworkCallback(), 0);
- } catch (RemoteException e) {
- unregisterNetworkCallback();
- }
- }
-
- @Override
- public void unregisterNetworkCallback() {
- Log.d(TAG, "unregistering network callback");
- if (mNetworkCallback != null) {
- mCm.unregisterNetworkCallback(mNetworkCallback);
- mNetworkCallback = null;
- }
- }
-
- @Override
- public int scheduleJob(JobInfo jobInfo) {
- final JobScheduler jobScheduler = getApplicationContext()
- .getSystemService(JobScheduler.class);
- return jobScheduler.schedule(jobInfo);
- }
- };
-
- @Override
- public IBinder onBind(Intent intent) {
- return mBinder;
- }
-
- @Override
- public void onCreate() {
- final Context context = getApplicationContext();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .createNotificationChannel(new NotificationChannel(NOTIFICATION_CHANNEL_ID,
- NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT));
- mCm = (ConnectivityManager) getApplicationContext()
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- }
-
- @Override
- public void onDestroy() {
- final Context context = getApplicationContext();
- ((NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE))
- .deleteNotificationChannel(NOTIFICATION_CHANNEL_ID);
- if (mReceiver != null) {
- Log.d(TAG, "onDestroy(): unregistering " + mReceiver);
- getApplicationContext().unregisterReceiver(mReceiver);
- }
-
- super.onDestroy();
- }
-}
diff --git a/tests/cts/hostside-network-policy/certs/Android.bp b/tests/cts/hostside-network-policy/certs/Android.bp
deleted file mode 100644
index bfbc341..0000000
--- a/tests/cts/hostside-network-policy/certs/Android.bp
+++ /dev/null
@@ -1,9 +0,0 @@
-package {
- default_team: "trendy_team_framework_backstage_power",
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_app_certificate {
- name: "cts-netpolicy-app",
- certificate: "cts-net-app",
-}
diff --git a/tests/cts/hostside-network-policy/certs/README b/tests/cts/hostside-network-policy/certs/README
deleted file mode 100644
index b660a82..0000000
--- a/tests/cts/hostside-network-policy/certs/README
+++ /dev/null
@@ -1,2 +0,0 @@
-# Generated with:
-development/tools/make_key cts-net-app '/CN=cts-net-app'
diff --git a/tests/cts/hostside-network-policy/certs/cts-net-app.pk8 b/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
deleted file mode 100644
index 1703e4e..0000000
--- a/tests/cts/hostside-network-policy/certs/cts-net-app.pk8
+++ /dev/null
Binary files differ
diff --git a/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem b/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
deleted file mode 100644
index a15ff48..0000000
--- a/tests/cts/hostside-network-policy/certs/cts-net-app.x509.pem
+++ /dev/null
@@ -1,19 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDAjCCAeqgAwIBAgIJAMhWwIIqr1r6MA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
-BAMMC2N0cy1uZXQtYXBwMB4XDTE4MDYyMDAyMjAwN1oXDTQ1MTEwNTAyMjAwN1ow
-FjEUMBIGA1UEAwwLY3RzLW5ldC1hcHAwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
-ggEKAoIBAQDefOayWQss1E+FQIONK6IhlXhe0BEyHshIrnPOOmuCPa/Svfbnmziy
-hr1KTjaQ3ET/mGShwlt6AUti7nKx9aB71IJp5mSBuwW62A8jvN3yNOo45YV8+n1o
-TrEoMWMf7hQmoOSqaSJ+VFuVms/kPSEh99okDgHCej6rsEkEcDoh6pJajQyUYDwR
-SNAF8SrqCDhqFbZW/LWedvuikCUlNtzuv7/GrcLcsiWEfHv7UOBKpMjLo9BhD1XF
-IefnxImcBQrQGMnE9TLixBiEeX5yauLgbZuxBqD/zsI2TH1FjxTeuJan83kLbqqH
-FgyvPaUjwckAdQPyom7ZUYFnBc0LQ9xzAgMBAAGjUzBRMB0GA1UdDgQWBBRZrBEw
-tAB2WNXj8dQ7ZOuJ34kY5DAfBgNVHSMEGDAWgBRZrBEwtAB2WNXj8dQ7ZOuJ34kY
-5DAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQDeI9AnLW6l/39y
-z96w/ldxZVFPzBRiFIsJsPHVyXlD5vUHZv/ju2jFn8TZSZR5TK0bzCEoVLp34Sho
-bbS0magP82yIvCRibyoyD+TDNnZkNJwjYnikE+/oyshTSQtpkn/rDA+0Y09BUC1E
-N2I6bV9pTXLFg7oah2FmqPRPzhgeYUKENgOQkrrjUCn6y0i/k374n7aftzdniSIz
-2kCRVEeN9gws6CnoMPx0vr32v/JVuPV6zfdJYadgj/eFRyTNE4msd9kE82Wc46eU
-YiI+LuXZ3ZMUNWGY7MK2pOUUS52JsBQ3K235dA5WaU4x8OBlY/WkNYX/eLbNs5jj
-FzLmhZZ1
------END CERTIFICATE-----
diff --git a/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp b/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
deleted file mode 100644
index cdede36..0000000
--- a/tests/cts/hostside-network-policy/instrumentation_arguments/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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: ["Android-Apache-2.0"],
-}
-
-filegroup {
- name: "ArgumentConstants",
- srcs: ["src/**/*.java"],
-}
diff --git a/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java b/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
deleted file mode 100644
index 0fe98e9..0000000
--- a/tests/cts/hostside-network-policy/instrumentation_arguments/src/com/android/cts/netpolicy/arguments/InstrumentationArguments.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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.cts.netpolicy.arguments;
-
-public interface InstrumentationArguments {
- String ARG_WAIVE_BIND_PRIORITY = "waive_bind_priority";
- String ARG_CONNECTION_CHECK_CUSTOM_URL = "connection_check_custom_url";
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
deleted file mode 100644
index 422231d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideConnOnActivityStartTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2022 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
-import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-
-import org.junit.Test;
-
-import java.util.Map;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideConnOnActivityStartTest extends HostsideNetworkPolicyTestCase {
- private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
-
- @BeforeClassWithInfo
- public static void setUpOnce(TestInformation testInfo) throws Exception {
- uninstallPackage(testInfo, TEST_APP2_PKG, false);
- installPackage(testInfo, TEST_APP2_APK);
- }
-
- @AfterClassWithInfo
- public static void tearDownOnce(TestInformation testInfo) throws DeviceNotAvailableException {
- uninstallPackage(testInfo, TEST_APP2_PKG, true);
- }
-
- @Test
- public void testStartActivity_batterySaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
- }
-
- @Test
- public void testStartActivity_dataSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
- }
-
- @FlakyTest(bugId = 231440256)
- @Test
- public void testStartActivity_doze() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
- }
-
- @Test
- public void testStartActivity_appStandby() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testStartActivity_default() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_CLASS, "testStartActivity_default",
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
deleted file mode 100644
index 62952bb..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideDefaultNetworkRestrictionsTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-// TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side tests.
-@FlakyTest(bugId = 288324467)
-public class HostsideDefaultNetworkRestrictionsTests extends HostsideNetworkPolicyTestCase {
- private static final String METERED_TEST_CLASS = TEST_PKG + ".DefaultRestrictionsMeteredTest";
- private static final String NON_METERED_TEST_CLASS =
- TEST_PKG + ".DefaultRestrictionsNonMeteredTest";
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- private void runMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithCustomOptions(TEST_PKG, METERED_TEST_CLASS, methodName,
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-
- private void runNonMeteredTest(String methodName) throws DeviceNotAvailableException {
- runDeviceTestsWithCustomOptions(TEST_PKG, NON_METERED_TEST_CLASS, methodName,
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
- throws Exception {
- runMeteredTest("testActivityNetworkAccess");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
- throws Exception {
- runMeteredTest("testFgsNetworkAccess");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
- runMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
- }
-
- @Test
- public void testMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
- throws Exception {
- runMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_testActivityNetworkAccess()
- throws Exception {
- runNonMeteredTest("testActivityNetworkAccess");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_testFgsNetworkAccess()
- throws Exception {
- runNonMeteredTest("testFgsNetworkAccess");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_inFullAllowlist() throws Exception {
- runNonMeteredTest("testBackgroundNetworkAccess_inFullAllowlist");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_defaultRestrictions_inExceptIdleAllowlist()
- throws Exception {
- runNonMeteredTest("testBackgroundNetworkAccess_inExceptIdleAllowlist");
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
deleted file mode 100644
index 2c2b118..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkCallbackTests.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2019 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import android.platform.test.annotations.FlakyTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideNetworkCallbackTests extends HostsideNetworkPolicyTestCase {
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @Test
- public void testOnBlockedStatusChanged_dataSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
- }
-
- @Test
- public void testOnBlockedStatusChanged_powerSaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testOnBlockedStatusChanged_default() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkCallbackTest",
- "testOnBlockedStatusChanged_default", Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
-
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
deleted file mode 100644
index 8ffe360..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyManagerTests.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2021 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_WAIVE_BIND_PRIORITY;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.Map;
-
-public class HostsideNetworkPolicyManagerTests extends HostsideNetworkPolicyTestCase {
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withUidNotBlocked() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withUidNotBlocked");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withSystemUid() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidNetworkingBlocked_withSystemUid");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withDataSaverMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withDataSaverMode");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withRestrictedNetworkingMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withRestrictedNetworkingMode");
- }
-
- @Test
- public void testIsUidNetworkingBlocked_withPowerSaverMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_withPowerSaverMode");
- }
-
- @Test
- public void testIsUidRestrictedOnMeteredNetworks() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG,
- TEST_PKG + ".NetworkPolicyManagerTest", "testIsUidRestrictedOnMeteredNetworks");
- }
-
- // TODO(b/321848487): Annotate with @RequiresFlagsEnabled to mirror the device-side test.
- @Test
- public void testIsUidNetworkingBlocked_whenInBackground() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".NetworkPolicyManagerTest",
- "testIsUidNetworkingBlocked_whenInBackground",
- Map.of(ARG_WAIVE_BIND_PRIORITY, "true"));
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
deleted file mode 100644
index 6de6b17..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideNetworkPolicyTestCase.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * 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.cts.netpolicy;
-
-import static com.android.cts.netpolicy.arguments.InstrumentationArguments.ARG_CONNECTION_CHECK_CUSTOM_URL;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import com.android.ddmlib.Log;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.targetprep.BuildError;
-import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
-import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.runner.RunWith;
-
-import java.util.Map;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-abstract class HostsideNetworkPolicyTestCase extends BaseHostJUnit4Test {
- protected static final boolean DEBUG = false;
- protected static final String TAG = "HostsideNetworkPolicyTests";
- protected static final String TEST_PKG = "com.android.cts.netpolicy.hostside";
- protected static final String TEST_APK = "CtsHostsideNetworkPolicyTestsApp.apk";
- protected static final String TEST_APP2_PKG = "com.android.cts.netpolicy.hostside.app2";
- protected static final String TEST_APP2_APK = "CtsHostsideNetworkPolicyTestsApp2.apk";
-
- @Option(name = "custom-url", importance = Option.Importance.IF_UNSET,
- description = "A custom url to use for testing network connections")
- protected String mCustomUrl;
-
- @BeforeClassWithInfo
- public static void setUpOnceBase(TestInformation testInfo) throws Exception {
- uninstallPackage(testInfo, TEST_PKG, false);
- installPackage(testInfo, TEST_APK);
- }
-
- @AfterClassWithInfo
- public static void tearDownOnceBase(TestInformation testInfo)
- throws DeviceNotAvailableException {
- uninstallPackage(testInfo, TEST_PKG, true);
- }
-
- // Custom static method to install the specified package, this is used to bypass auto-cleanup
- // per test in BaseHostJUnit4.
- protected static void installPackage(TestInformation testInfo, String apk)
- throws DeviceNotAvailableException, TargetSetupError {
- assertNotNull(testInfo);
- final int userId = testInfo.getDevice().getCurrentUser();
- final SuiteApkInstaller installer = new SuiteApkInstaller();
- // Force the apk clean up
- installer.setCleanApk(true);
- installer.addTestFileName(apk);
- installer.setUserId(userId);
- installer.setShouldGrantPermission(true);
- installer.addInstallArg("-t");
- try {
- installer.setUp(testInfo);
- } catch (BuildError e) {
- throw new TargetSetupError(
- e.getMessage(), e, testInfo.getDevice().getDeviceDescriptor(), e.getErrorId());
- }
- }
-
- protected void installPackage(String apk) throws DeviceNotAvailableException, TargetSetupError {
- installPackage(getTestInformation(), apk);
- }
-
- protected static void uninstallPackage(TestInformation testInfo, String packageName,
- boolean shouldSucceed)
- throws DeviceNotAvailableException {
- assertNotNull(testInfo);
- final String result = testInfo.getDevice().uninstallPackage(packageName);
- if (shouldSucceed) {
- assertNull("uninstallPackage(" + packageName + ") failed: " + result, result);
- }
- }
-
- protected void uninstallPackage(String packageName,
- boolean shouldSucceed)
- throws DeviceNotAvailableException {
- uninstallPackage(getTestInformation(), packageName, shouldSucceed);
- }
-
- protected void assertPackageUninstalled(String packageName) throws DeviceNotAvailableException {
- final String command = "cmd package list packages " + packageName;
- final int max_tries = 5;
- for (int i = 1; i <= max_tries; i++) {
- final String result = runCommand(command);
- if (result.trim().isEmpty()) {
- return;
- }
- // 'list packages' filters by substring, so we need to iterate with the results
- // and check one by one, otherwise 'com.android.cts.netpolicy.hostside' could return
- // 'com.android.cts.netpolicy.hostside.app2'
- boolean found = false;
- for (String line : result.split("[\\r\\n]+")) {
- if (line.endsWith(packageName)) {
- found = true;
- break;
- }
- }
- if (!found) {
- return;
- }
- Log.v(TAG, "Package " + packageName + " not uninstalled yet (" + result
- + "); sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("Package '" + packageName + "' not uinstalled after " + max_tries + " seconds");
- }
-
- protected int getUid(String packageName) throws DeviceNotAvailableException {
- final int currentUser = getDevice().getCurrentUser();
- final String uidLines = runCommand(
- "cmd package list packages -U --user " + currentUser + " " + packageName);
- for (String uidLine : uidLines.split("\n")) {
- if (uidLine.startsWith("package:" + packageName + " uid:")) {
- final String[] uidLineParts = uidLine.split(":");
- // 3rd entry is package uid
- return Integer.parseInt(uidLineParts[2].trim());
- }
- }
- throw new IllegalStateException("Failed to find the test app on the device; pkg="
- + packageName + ", u=" + currentUser);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className)
- throws DeviceNotAvailableException {
- return runDeviceTestsWithCustomOptions(packageName, className, null);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
- String methodName) throws DeviceNotAvailableException {
- return runDeviceTestsWithCustomOptions(packageName, className, methodName, null);
- }
-
- protected boolean runDeviceTestsWithCustomOptions(String packageName, String className,
- String methodName, Map<String, String> testArgs) throws DeviceNotAvailableException {
- final DeviceTestRunOptions deviceTestRunOptions = new DeviceTestRunOptions(packageName)
- .setTestClassName(className)
- .setTestMethodName(methodName);
-
- // Currently there is only one custom option that the test exposes.
- if (mCustomUrl != null) {
- deviceTestRunOptions.addInstrumentationArg(ARG_CONNECTION_CHECK_CUSTOM_URL, mCustomUrl);
- }
- // Pass over any test specific arguments.
- if (testArgs != null) {
- for (Map.Entry<String, String> arg : testArgs.entrySet()) {
- deviceTestRunOptions.addInstrumentationArg(arg.getKey(), arg.getValue());
- }
- }
- return runDeviceTests(deviceTestRunOptions);
- }
-
- protected String runCommand(String command) throws DeviceNotAvailableException {
- Log.d(TAG, "Command: '" + command + "'");
- final String output = getDevice().executeShellCommand(command);
- if (DEBUG) Log.v(TAG, "Output: " + output.trim());
- return output;
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
deleted file mode 100644
index 0261c7d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/HostsideRestrictBackgroundNetworkTests.java
+++ /dev/null
@@ -1,466 +0,0 @@
-/*
- * Copyright (C) 2016 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.cts.netpolicy;
-
-import static org.junit.Assert.fail;
-
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.SecurityTest;
-
-import com.android.ddmlib.Log;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@FlakyTest(bugId = 288324467)
-public class HostsideRestrictBackgroundNetworkTests extends HostsideNetworkPolicyTestCase {
-
- @Before
- public void setUp() throws Exception {
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @After
- public void tearDown() throws Exception {
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- @SecurityTest
- @Test
- public void testDataWarningReceiver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataWarningReceiverTest",
- "testSnoozeWarningNotReceived");
- }
-
- /**************************
- * Data Saver Mode tests. *
- **************************/
-
- @Test
- public void testDataSaverMode_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_disabled");
- }
-
- @Test
- public void testDataSaverMode_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_whitelisted");
- }
-
- @Test
- public void testDataSaverMode_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_enabled");
- }
-
- @Test
- public void testDataSaverMode_blacklisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_blacklisted");
- }
-
- @Test
- public void testDataSaverMode_reinstall() throws Exception {
- final int oldUid = getUid(TEST_APP2_PKG);
-
- // Make sure whitelist is revoked when package is removed
- addRestrictBackgroundWhitelist(oldUid);
-
- uninstallPackage(TEST_APP2_PKG, true);
- assertPackageUninstalled(TEST_APP2_PKG);
- assertRestrictBackgroundWhitelist(oldUid, false);
-
- installPackage(TEST_APP2_APK);
- final int newUid = getUid(TEST_APP2_PKG);
- assertRestrictBackgroundWhitelist(oldUid, false);
- assertRestrictBackgroundWhitelist(newUid, false);
- }
-
- @Test
- public void testDataSaverMode_requiredWhitelistedPackages() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testGetRestrictBackgroundStatus_requiredWhitelistedPackages");
- }
-
- @Test
- public void testDataSaverMode_broadcastNotSentOnUnsupportedDevices() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DataSaverModeTest",
- "testBroadcastNotSentOnUnsupportedDevices");
- }
-
- /*****************************
- * Battery Saver Mode tests. *
- *****************************/
-
- @Test
- public void testBatterySaverModeMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testBatterySaverModeMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testBatterySaverModeMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testBatterySaverMode_reinstall() throws Exception {
- if (!isDozeModeEnabled()) {
- Log.w(TAG, "testBatterySaverMode_reinstall() skipped because device does not support "
- + "Doze Mode");
- return;
- }
-
- addPowerSaveModeWhitelist(TEST_APP2_PKG);
-
- uninstallPackage(TEST_APP2_PKG, true);
- assertPackageUninstalled(TEST_APP2_PKG);
- assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
-
- installPackage(TEST_APP2_APK);
- assertPowerSaveModeWhitelist(TEST_APP2_PKG, false);
- }
-
- @Test
- public void testBatterySaverModeNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testBatterySaverModeNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testBatterySaverModeNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".BatterySaverModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- /*******************
- * App idle tests. *
- *******************/
-
- @Test
- public void testAppIdleMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testAppIdleMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testAppIdleMetered_tempWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_tempWhitelisted");
- }
-
- @Test
- public void testAppIdleMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testAppIdleMetered_idleWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testAppIdleNetworkAccess_idleWhitelisted");
- }
-
- // TODO: currently power-save mode and idle uses the same whitelist, so this test would be
- // redundant (as it would be testing the same as testBatterySaverMode_reinstall())
- // public void testAppIdle_reinstall() throws Exception {
- // }
-
- @Test
- public void testAppIdleNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
-
- @Test
- public void testAppIdleNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_tempWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_tempWhitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testAppIdleNonMetered_idleWhitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdleNetworkAccess_idleWhitelisted");
- }
-
- @Test
- public void testAppIdleNonMetered_whenCharging() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdleNetworkAccess_whenCharging");
- }
-
- @Test
- public void testAppIdleMetered_whenCharging() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
- "testAppIdleNetworkAccess_whenCharging");
- }
-
- @Test
- public void testAppIdle_toast() throws Exception {
- // Check that showing a toast doesn't bring an app out of standby
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
- "testAppIdle_toast");
- }
-
- /********************
- * Doze Mode tests. *
- ********************/
-
- @Test
- public void testDozeModeMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testDozeModeMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testDozeModeMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testDozeModeMetered_enabledButWhitelistedOnNotificationAction() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeMeteredTest",
- "testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
- }
-
- // TODO: currently power-save mode and idle uses the same whitelist, so this test would be
- // redundant (as it would be testing the same as testBatterySaverMode_reinstall())
- // public void testDozeMode_reinstall() throws Exception {
- // }
-
- @Test
- public void testDozeModeNonMetered_disabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_disabled");
- }
-
- @Test
- public void testDozeModeNonMetered_whitelisted() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_whitelisted");
- }
-
- @Test
- public void testDozeModeNonMetered_enabled() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabled");
- }
-
- @Test
- public void testDozeModeNonMetered_enabledButWhitelistedOnNotificationAction()
- throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".DozeModeNonMeteredTest",
- "testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction");
- }
-
- /**********************
- * Mixed modes tests. *
- **********************/
-
- @Test
- public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDataAndBatterySaverModes_meteredNetwork");
- }
-
- @Test
- public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDataAndBatterySaverModes_nonMeteredNetwork");
- }
-
- @Test
- public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndBatterySaverMode_powerSaveWhitelists");
- }
-
- @Test
- public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndAppIdle_powerSaveWhitelists");
- }
-
- @Test
- public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndDoze_tempPowerSaveWhitelists");
- }
-
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndBatterySaver_tempPowerSaveWhitelists");
- }
-
- @Test
- public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testDozeAndAppIdle_appIdleWhitelist");
- }
-
- @Test
- public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists");
- }
-
- @Test
- public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".MixedModesTest",
- "testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
- }
-
- /**************************
- * Restricted mode tests. *
- **************************/
-
- @Test
- public void testNetworkAccess_restrictedMode() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
- "testNetworkAccess");
- }
-
- @Test
- public void testNetworkAccess_restrictedMode_withBatterySaver() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
- "testNetworkAccess_withBatterySaver");
- }
-
- /************************
- * Expedited job tests. *
- ************************/
-
- @Test
- public void testMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobMeteredTest");
- }
-
- @Test
- public void testNonMeteredNetworkAccess_expeditedJob() throws Exception {
- runDeviceTestsWithCustomOptions(TEST_PKG, TEST_PKG + ".ExpeditedJobNonMeteredTest");
- }
-
- /*******************
- * Helper methods. *
- *******************/
-
- private void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
- final int max_tries = 5;
- boolean actual = false;
- for (int i = 1; i <= max_tries; i++) {
- final String output = runCommand("cmd netpolicy list restrict-background-whitelist ");
- actual = output.contains(Integer.toString(uid));
- if (expected == actual) {
- return;
- }
- Log.v(TAG, "whitelist check for uid " + uid + " doesn't match yet (expected "
- + expected + ", got " + actual + "); sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("whitelist check for uid " + uid + " failed: expected "
- + expected + ", got " + actual);
- }
-
- private void assertPowerSaveModeWhitelist(String packageName, boolean expected)
- throws Exception {
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- assertDelayedCommand("dumpsys deviceidle whitelist =" + packageName,
- Boolean.toString(expected));
- }
-
- /**
- * Asserts the result of a command, wait and re-running it a couple times if necessary.
- */
- private void assertDelayedCommand(String command, String expectedResult)
- throws InterruptedException, DeviceNotAvailableException {
- final int maxTries = 5;
- for (int i = 1; i <= maxTries; i++) {
- final String result = runCommand(command).trim();
- if (result.equals(expectedResult)) return;
- Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
- + expectedResult + "' on attempt #; sleeping 1s before polling again");
- RunUtil.getDefault().sleep(1000);
- }
- fail("Command '" + command + "' did not return '" + expectedResult + "' after " + maxTries
- + " attempts");
- }
-
- protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
- runCommand("cmd netpolicy add restrict-background-whitelist " + uid);
- assertRestrictBackgroundWhitelist(uid, true);
- }
-
- private void addPowerSaveModeWhitelist(String packageName) throws Exception {
- Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
- // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
- // need to use netpolicy for whitelisting
- runCommand("dumpsys deviceidle whitelist +" + packageName);
- assertPowerSaveModeWhitelist(packageName, true);
- }
-
- protected boolean isDozeModeEnabled() throws Exception {
- final String result = runCommand("cmd deviceidle enabled deep").trim();
- return result.equals("1");
- }
-}
diff --git a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java b/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
deleted file mode 100644
index cbf2f4d..0000000
--- a/tests/cts/hostside-network-policy/src/com/android/cts/netpolicy/NetworkPolicyTestsPreparer.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * 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 com.android.cts.netpolicy;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.targetprep.ITargetPreparer;
-
-public class NetworkPolicyTestsPreparer implements ITargetPreparer {
- private ITestDevice mDevice;
- private boolean mOriginalAirplaneModeEnabled;
- private String mOriginalAppStandbyEnabled;
- private String mOriginalBatteryStatsConstants;
- private final static String KEY_STABLE_CHARGING_DELAY_MS = "battery_charged_delay_ms";
- private final static int DESIRED_STABLE_CHARGING_DELAY_MS = 0;
-
- @Override
- public void setUp(TestInformation testInformation) throws DeviceNotAvailableException {
- mDevice = testInformation.getDevice();
- mOriginalAppStandbyEnabled = getAppStandbyEnabled();
- setAppStandbyEnabled("1");
- LogUtil.CLog.d("Original app_standby_enabled: " + mOriginalAppStandbyEnabled);
-
- mOriginalBatteryStatsConstants = getBatteryStatsConstants();
- setBatteryStatsConstants(
- KEY_STABLE_CHARGING_DELAY_MS + "=" + DESIRED_STABLE_CHARGING_DELAY_MS);
- LogUtil.CLog.d("Original battery_saver_constants: " + mOriginalBatteryStatsConstants);
-
- mOriginalAirplaneModeEnabled = getAirplaneModeEnabled();
- // Turn off airplane mode in case another test left the device in that state.
- setAirplaneModeEnabled(false);
- LogUtil.CLog.d("Original airplane mode state: " + mOriginalAirplaneModeEnabled);
- }
-
- @Override
- public void tearDown(TestInformation testInformation, Throwable e)
- throws DeviceNotAvailableException {
- setAirplaneModeEnabled(mOriginalAirplaneModeEnabled);
- setAppStandbyEnabled(mOriginalAppStandbyEnabled);
- setBatteryStatsConstants(mOriginalBatteryStatsConstants);
- }
-
- private void setAirplaneModeEnabled(boolean enable) throws DeviceNotAvailableException {
- executeCmd("cmd connectivity airplane-mode " + (enable ? "enable" : "disable"));
- }
-
- private boolean getAirplaneModeEnabled() throws DeviceNotAvailableException {
- return "enabled".equals(executeCmd("cmd connectivity airplane-mode").trim());
- }
-
- private void setAppStandbyEnabled(String appStandbyEnabled) throws DeviceNotAvailableException {
- if ("null".equals(appStandbyEnabled)) {
- executeCmd("settings delete global app_standby_enabled");
- } else {
- executeCmd("settings put global app_standby_enabled " + appStandbyEnabled);
- }
- }
-
- private String getAppStandbyEnabled() throws DeviceNotAvailableException {
- return executeCmd("settings get global app_standby_enabled").trim();
- }
-
- private void setBatteryStatsConstants(String batteryStatsConstants)
- throws DeviceNotAvailableException {
- executeCmd("settings put global battery_stats_constants \"" + batteryStatsConstants + "\"");
- }
-
- private String getBatteryStatsConstants() throws DeviceNotAvailableException {
- return executeCmd("settings get global battery_stats_constants");
- }
-
- private String executeCmd(String cmd) throws DeviceNotAvailableException {
- final String output = mDevice.executeShellCommand(cmd).trim();
- LogUtil.CLog.d("Output for '%s': %s", cmd, output);
- return output;
- }
-}
diff --git a/tests/cts/multidevices/Android.bp b/tests/cts/multidevices/Android.bp
index dc90adb..5f062f1 100644
--- a/tests/cts/multidevices/Android.bp
+++ b/tests/cts/multidevices/Android.bp
@@ -19,9 +19,11 @@
python_test_host {
name: "CtsConnectivityMultiDevicesTestCases",
- main: "connectivity_multi_devices_test.py",
+ main: "run_tests.py",
srcs: [
+ "apfv4_test.py",
"connectivity_multi_devices_test.py",
+ "run_tests.py",
],
libs: [
"mobly",
diff --git a/tests/cts/multidevices/apfv4_test.py b/tests/cts/multidevices/apfv4_test.py
new file mode 100644
index 0000000..4633d37
--- /dev/null
+++ b/tests/cts/multidevices/apfv4_test.py
@@ -0,0 +1,35 @@
+# 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.
+
+from net_tests_utils.host.python import apf_test_base
+
+# Constants.
+COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
+ETHER_BROADCAST_ADDR = "FFFFFFFFFFFF"
+ETH_P_ETHERCAT = "88A4"
+
+
+class ApfV4Test(apf_test_base.ApfTestBase):
+
+ def test_apf_drop_ethercat(self):
+ # Ethernet header (14 bytes).
+ packet = ETHER_BROADCAST_ADDR # Destination MAC (broadcast)
+ packet += self.server_mac_address.replace(":", "") # Source MAC
+ packet += ETH_P_ETHERCAT # EtherType (EtherCAT)
+
+ # EtherCAT header (2 bytes) + 44 bytes of zero padding.
+ packet += "00" * 46
+ self.send_packet_and_expect_counter_increased(
+ packet, COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED
+ )
diff --git a/tests/cts/multidevices/connectivity_multi_devices_test.py b/tests/cts/multidevices/connectivity_multi_devices_test.py
index 7e7bbf5..eceb535 100644
--- a/tests/cts/multidevices/connectivity_multi_devices_test.py
+++ b/tests/cts/multidevices/connectivity_multi_devices_test.py
@@ -1,40 +1,24 @@
-# Lint as: python3
-"""Connectivity multi devices tests."""
-import sys
+# 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.
-from mobly import asserts
-from mobly import base_test
-from mobly import test_runner
-from mobly import utils
-from mobly.controllers import android_device
-from net_tests_utils.host.python import adb_utils, apf_utils, assert_utils, mdns_utils, tether_utils
+from net_tests_utils.host.python import mdns_utils, multi_devices_test_base, tether_utils
from net_tests_utils.host.python.tether_utils import UpstreamType
-CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE = "com.google.snippet.connectivity"
-COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED = "DROPPED_ETHERTYPE_NOT_ALLOWED"
-
-class ConnectivityMultiDevicesTest(base_test.BaseTestClass):
-
- def setup_class(self):
- # Declare that two Android devices are needed.
- self.clientDevice, self.serverDevice = self.register_controller(
- android_device, min_number=2
- )
-
- def setup_device(device):
- device.load_snippet(
- "connectivity_multi_devices_snippet",
- CONNECTIVITY_MULTI_DEVICES_SNIPPET_PACKAGE,
- )
-
- # Set up devices in parallel to save time.
- utils.concurrent_exec(
- setup_device,
- ((self.clientDevice,), (self.serverDevice,)),
- max_workers=2,
- raise_on_exception=True,
- )
+class ConnectivityMultiDevicesTest(
+ multi_devices_test_base.MultiDevicesTestBase
+):
def test_hotspot_upstream_wifi(self):
tether_utils.assume_hotspot_test_preconditions(
@@ -84,54 +68,3 @@
tether_utils.cleanup_tethering_for_upstream_type(
self.serverDevice, UpstreamType.NONE
)
-
- def test_apf_drop_ethercat(self):
- tether_utils.assume_hotspot_test_preconditions(
- self.serverDevice, self.clientDevice, UpstreamType.NONE
- )
- client = self.clientDevice.connectivity_multi_devices_snippet
- try:
- server_iface_name, client_network = (
- tether_utils.setup_hotspot_and_client_for_upstream_type(
- self.serverDevice, self.clientDevice, UpstreamType.NONE
- )
- )
- client_iface_name = client.getInterfaceNameFromNetworkHandle(client_network)
-
- adb_utils.set_doze_mode(self.clientDevice, True)
-
- count_before_test = apf_utils.get_apf_counter(
- self.clientDevice,
- client_iface_name,
- COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
- )
- try:
- apf_utils.send_broadcast_empty_ethercat_packet(
- self.serverDevice, server_iface_name
- )
- except apf_utils.UnsupportedOperationException:
- asserts.skip(
- "NetworkStack is too old to support send raw packet, skip test."
- )
-
- assert_utils.expect_with_retry(
- lambda: apf_utils.get_apf_counter(
- self.clientDevice,
- client_iface_name,
- COUNTER_DROPPED_ETHERTYPE_NOT_ALLOWED,
- )
- > count_before_test
- )
- finally:
- adb_utils.set_doze_mode(self.clientDevice, False)
- tether_utils.cleanup_tethering_for_upstream_type(
- self.serverDevice, UpstreamType.NONE
- )
-
-
-if __name__ == "__main__":
- # Take test args
- if "--" in sys.argv:
- index = sys.argv.index("--")
- sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
- test_runner.main()
diff --git a/tests/cts/multidevices/run_tests.py b/tests/cts/multidevices/run_tests.py
new file mode 100644
index 0000000..1391d13
--- /dev/null
+++ b/tests/cts/multidevices/run_tests.py
@@ -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.
+
+"""Main entrypoint for all of test cases."""
+
+import sys
+from apfv4_test import ApfV4Test
+from connectivity_multi_devices_test import ConnectivityMultiDevicesTest
+from mobly import suite_runner
+
+
+if __name__ == "__main__":
+ # For MoblyBinaryHostTest, this entry point will be called twice:
+ # 1. List tests.
+ # <mobly-par-file-name> -- --list_tests
+ # 2. Run tests.
+ # <mobly-par-file-name> -- --config=<yaml-path> \
+ # --device_serial=<device-serial> --log_path=<log-path>
+ # Strip the "--" since suite runner doesn't recognize it.
+ # While the parameters before "--" is for the infrastructure,
+ # ignore them if any. Also, do not alter parameters if there
+ # is no "--", in case the binary is invoked manually.
+ if "--" in sys.argv:
+ index = sys.argv.index("--")
+ sys.argv = sys.argv[:1] + sys.argv[index + 1 :]
+ # TODO: make the tests can be executed without manually list classes.
+ suite_runner.run_suite([ConnectivityMultiDevicesTest, ApfV4Test], sys.argv)
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 024d3bf..24431a6 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -25,6 +25,7 @@
<option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.tethering.apex" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+ <option name="has-server-side-config" value="false" />
<option name="target" value="device" />
<option name="config-filename" value="{MODULE}" />
<option name="version" value="1.0" />
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 5662fca..9ac2c67 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -410,6 +410,12 @@
}
}
+ private fun installAndVerifyProgram(program: ByteArray) {
+ installProgram(program)
+ val readResult = readProgram().take(program.size).toByteArray()
+ assertThat(readResult).isEqualTo(program)
+ }
+
fun ApfV4GeneratorBase<*>.addPassIfNotIcmpv6EchoReply() {
// If not IPv6 -> PASS
addLoad16(R0, ETH_ETHERTYPE_OFFSET)
@@ -436,12 +442,7 @@
assumeApfVersionSupportAtLeast(4)
// clear any active APF filter
- var gen = ApfV4Generator(
- caps.apfVersionSupported,
- caps.maximumApfProgramSize,
- caps.maximumApfProgramSize
- ).addPass()
- installProgram(gen.generate())
+ clearApfMemory()
readProgram() // wait for install completion
// Assert that initial ping does not get filtered.
@@ -455,7 +456,7 @@
assertThat(packetReader.expectPingReply()).isEqualTo(data)
// Generate an APF program that drops the next ping
- gen = ApfV4Generator(
+ val gen = ApfV4Generator(
caps.apfVersionSupported,
caps.maximumApfProgramSize,
caps.maximumApfProgramSize
@@ -472,8 +473,7 @@
gen.addJump(BaseApfGenerator.DROP_LABEL)
val program = gen.generate()
- installProgram(program)
- readProgram() // wait for install completion
+ installAndVerifyProgram(program)
packetReader.sendPing(data, payloadSize)
packetReader.expectPingDropped()
@@ -518,8 +518,7 @@
val program = gen.generate()
assertThat(program.size).isLessThan(counterRegion)
- installProgram(program)
- readProgram() // wait for install completion
+ installAndVerifyProgram(program)
// Trigger the program by sending a ping and waiting on the reply.
val payloadSize = if (getFirstApiLevel() >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
@@ -567,8 +566,7 @@
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_SECONDS)
gen.addStoreData(R0, 0)
- installProgram(gen.generate())
- readProgram() // wait for install completion
+ installAndVerifyProgram(gen.generate())
val payloadSize = 56
val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
@@ -608,8 +606,7 @@
gen.addLoadFromMemory(R0, MemorySlot.FILTER_AGE_16384THS)
gen.addStoreCounter(FILTER_AGE_16384THS, R0)
- installProgram(gen.generate())
- readProgram() // wait for install completion
+ installAndVerifyProgram(gen.generate())
val payloadSize = 56
val data = ByteArray(payloadSize).also { Random.nextBytes(it) }
@@ -717,8 +714,7 @@
.addPass()
.generate()
- installProgram(program)
- readProgram() // Ensure installation is complete
+ installAndVerifyProgram(program)
packetReader.sendPing(payload, payloadSize)
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 9458460..88c2d5a 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -206,6 +206,7 @@
import com.android.networkstack.apishim.common.ConnectivityManagerShim;
import com.android.testutils.AutoReleaseNetworkCallbackRule;
import com.android.testutils.CompatUtil;
+import com.android.testutils.ConnectUtil;
import com.android.testutils.ConnectivityModuleTest;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -2942,20 +2943,10 @@
// This may also apply to wifi in principle, but in practice methods that mock validation
// URL all disconnect wifi forcefully anyway, so don't wait for wifi to validate.
if (mPackageManager.hasSystemFeature(FEATURE_TELEPHONY)) {
- ensureValidatedNetwork(makeCellNetworkRequest());
+ new ConnectUtil(mContext).ensureCellularValidated();
}
}
- private void ensureValidatedNetwork(NetworkRequest request) {
- final TestableNetworkCallback cb = new TestableNetworkCallback();
- mCm.registerNetworkCallback(request, cb);
- cb.eventuallyExpect(CallbackEntry.NETWORK_CAPS_UPDATED,
- NETWORK_CALLBACK_TIMEOUT_MS,
- entry -> ((CallbackEntry.CapabilitiesChanged) entry).getCaps()
- .hasCapability(NET_CAPABILITY_VALIDATED));
- mCm.unregisterNetworkCallback(cb);
- }
-
@AppModeFull(reason = "WRITE_DEVICE_CONFIG permission can't be granted to instant apps")
@Test
public void testAcceptPartialConnectivity_validatedNetwork() throws Exception {
diff --git a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
index b5f43d3..e94d94f 100644
--- a/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/IpSecManagerTest.java
@@ -451,9 +451,10 @@
long uidTxDelta = 0;
long uidRxDelta = 0;
for (int i = 0; i < 100; i++) {
- // Clear TrafficStats cache is needed to avoid rate-limit caching for
- // TrafficStats API results on V+ devices.
- if (SdkLevel.isAtLeastV()) {
+ // Since the target SDK of this test should always equal or be larger than V,
+ // TrafficStats caching is always enabled. Clearing the cache is needed to
+ // avoid rate-limiting on devices with a mainlined (T+) NetworkStatsService.
+ if (SdkLevel.isAtLeastT()) {
runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
}
uidTxDelta = TrafficStats.getUidTxPackets(Os.getuid()) - uidTxPackets;
@@ -530,9 +531,10 @@
}
private static void initStatsChecker() throws Exception {
- // Clear TrafficStats cache is needed to avoid rate-limit caching for
- // TrafficStats API results on V+ devices.
- if (SdkLevel.isAtLeastV()) {
+ // Since the target SDK of this test should always equal or be larger than V,
+ // TrafficStats caching is always enabled. Clearing the cache is needed to
+ // avoid rate-limiting on devices with a mainlined (T+) NetworkStatsService.
+ if (SdkLevel.isAtLeastT()) {
runAsShell(NETWORK_SETTINGS, () -> TrafficStats.clearRateLimitCaches());
}
uidTxBytes = TrafficStats.getUidTxBytes(Os.getuid());
diff --git a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
index 621af23..f9acb66 100644
--- a/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkValidationTest.kt
@@ -19,7 +19,6 @@
import android.Manifest.permission.MANAGE_TEST_NETWORKS
import android.Manifest.permission.NETWORK_SETTINGS
import android.content.Context
-import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.EthernetManager
import android.net.InetAddresses
@@ -36,7 +35,6 @@
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_DISCOVER
import android.net.dhcp.DhcpPacket.DHCP_MESSAGE_TYPE_REQUEST
import android.net.dhcp.DhcpRequestPacket
-import android.os.Build
import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull
import androidx.test.platform.app.InstrumentationRegistry
@@ -44,7 +42,7 @@
import com.android.net.module.util.Inet4AddressUtils.getBroadcastAddress
import com.android.net.module.util.Inet4AddressUtils.getPrefixMaskAsInet4Address
import com.android.net.module.util.NetworkStackConstants.IPV4_ADDR_ANY
-import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.AutoCloseTestInterfaceRule
import com.android.testutils.DhcpClientPacketFilter
import com.android.testutils.DhcpOptionFilter
import com.android.testutils.RecorderCallback.CallbackEntry
@@ -79,10 +77,6 @@
@AppModeFull(reason = "Instant apps cannot create test networks")
@RunWith(AndroidJUnit4::class)
class NetworkValidationTest {
- @JvmField
- @Rule
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
-
private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
private val tnm by lazy { context.assertHasService(TestNetworkManager::class.java) }
private val eth by lazy { context.assertHasService(EthernetManager::class.java) }
@@ -104,23 +98,24 @@
private var testSkipped = false
+ @get:Rule
+ val testInterfaceRule = AutoCloseTestInterfaceRule(context)
+
@Before
fun setUp() {
// This test requires using a tap interface as an ethernet interface.
- val pm = context.getPackageManager()
- testSkipped = !pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET) &&
- context.getSystemService(EthernetManager::class.java) == null
+ testSkipped = context.getSystemService(EthernetManager::class.java) == null
assumeFalse(testSkipped)
// Register a request so the network does not get torn down
cm.requestNetwork(ethRequest, ethRequestCb)
runAsShell(NETWORK_SETTINGS, MANAGE_TEST_NETWORKS) {
eth.setIncludeTestInterfaces(true)
- // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
- // does not go out of scope, which would cause it to close the underlying FileDescriptor
- // in its finalizer.
- iface = tnm.createTapInterface()
}
+ // Keeping a reference to the test interface also makes sure the ParcelFileDescriptor
+ // does not go out of scope, which would cause it to close the underlying FileDescriptor
+ // in its finalizer.
+ iface = testInterfaceRule.createTapInterface()
handlerThread.start()
reader = TapPacketReader(
@@ -147,8 +142,6 @@
handlerThread.threadHandler.post { reader.stop() }
handlerThread.quitSafely()
handlerThread.join()
-
- iface.fileDescriptor.close()
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
index f45f881..24af42b 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerDownstreamTetheringTest.kt
@@ -17,19 +17,21 @@
import android.net.EthernetTetheringTestBase
import android.net.LinkAddress
-import android.net.TestNetworkInterface
import android.net.TetheringManager.CONNECTIVITY_SCOPE_LOCAL
import android.net.TetheringManager.TETHERING_ETHERNET
import android.net.TetheringManager.TetheringRequest
+import android.net.cts.util.EthernetTestInterface
import android.net.nsd.NsdManager
import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
import android.platform.test.annotations.AppModeFull
import androidx.test.filters.SmallTest
+import com.android.testutils.AutoCloseTestInterfaceRule
import com.android.testutils.ConnectivityModuleTest
import com.android.testutils.DevSdkIgnoreRule
import com.android.testutils.DevSdkIgnoreRunner
import com.android.testutils.NsdDiscoveryRecord
-import com.android.testutils.TapPacketReader
import com.android.testutils.pollForQuery
import com.android.testutils.tryTest
import java.util.Random
@@ -38,9 +40,12 @@
import org.junit.After
import org.junit.Assume.assumeFalse
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+private const val TAG = "NsdManagerDownstreamTetheringTest"
+
@RunWith(DevSdkIgnoreRunner::class)
@SmallTest
@ConnectivityModuleTest
@@ -50,14 +55,29 @@
private val nsdManager by lazy { context.getSystemService(NsdManager::class.java)!! }
private val serviceType = "_nmt%09d._tcp".format(Random().nextInt(1_000_000_000))
+ private val handlerThread = HandlerThread("$TAG thread").apply { start() }
+ private val handler = Handler(handlerThread.looper)
+ private lateinit var downstreamIface: EthernetTestInterface
+ private var tetheringEventCallback: MyTetheringEventCallback? = null
+
+ @get:Rule
+ val testInterfaceRule = AutoCloseTestInterfaceRule(context)
+
@Before
override fun setUp() {
super.setUp()
- setIncludeTestInterfaces(true)
+ val iface = testInterfaceRule.createTapInterface()
+ downstreamIface = EthernetTestInterface(context, handler, iface)
}
@After
override fun tearDown() {
+ if (::downstreamIface.isInitialized) {
+ downstreamIface.destroy()
+ }
+ handlerThread.quitSafely()
+ handlerThread.join()
+ maybeUnregisterTetheringEventCallback(tetheringEventCallback)
super.tearDown()
}
@@ -65,16 +85,11 @@
fun testMdnsDiscoveryCanSendPacketOnLocalOnlyDownstreamTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- var downstreamIface: TestNetworkInterface? = null
- var tetheringEventCallback: MyTetheringEventCallback? = null
- var downstreamReader: TapPacketReader? = null
-
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
- downstreamIface = createTestInterface()
val iface = mTetheredInterfaceRequester.getInterface()
- assertEquals(iface, downstreamIface?.interfaceName)
+ assertEquals(downstreamIface.name, iface)
val request = TetheringRequest.Builder(TETHERING_ETHERNET)
.setConnectivityScope(CONNECTIVITY_SCOPE_LOCAL).build()
tetheringEventCallback = enableEthernetTethering(
@@ -83,23 +98,15 @@
).apply {
awaitInterfaceLocalOnly()
}
- // This shouldn't be flaky because the TAP interface will buffer all packets even
- // before the reader is started.
- downstreamReader = makePacketReader(downstreamIface)
+ val downstreamReader = downstreamIface.packetReader
waitForRouterAdvertisement(downstreamReader, iface, WAIT_RA_TIMEOUT_MS)
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
- assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+ assertNotNull(downstreamReader.pollForQuery("$serviceType.local", 12 /* type PTR */))
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
- } cleanupStep {
- maybeStopTapPacketReader(downstreamReader)
- } cleanupStep {
- maybeCloseTestInterface(downstreamIface)
- } cleanup {
- maybeUnregisterTetheringEventCallback(tetheringEventCallback)
}
}
@@ -107,16 +114,11 @@
fun testMdnsDiscoveryWorkOnTetheringInterface() {
assumeFalse(isInterfaceForTetheringAvailable())
- var downstreamIface: TestNetworkInterface? = null
- var tetheringEventCallback: MyTetheringEventCallback? = null
- var downstreamReader: TapPacketReader? = null
-
val discoveryRecord = NsdDiscoveryRecord()
tryTest {
- downstreamIface = createTestInterface()
val iface = mTetheredInterfaceRequester.getInterface()
- assertEquals(iface, downstreamIface?.interfaceName)
+ assertEquals(downstreamIface.name, iface)
val localAddr = LinkAddress("192.0.2.3/28")
val clientAddr = LinkAddress("192.0.2.2/28")
@@ -130,23 +132,14 @@
awaitInterfaceTethered()
}
- val fd = downstreamIface?.fileDescriptor?.fileDescriptor
- assertNotNull(fd)
- downstreamReader = makePacketReader(fd, getMTU(downstreamIface))
-
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStarted>()
- assertNotNull(downstreamReader?.pollForQuery("$serviceType.local", 12 /* type PTR */))
+ val downstreamReader = downstreamIface.packetReader
+ assertNotNull(downstreamReader.pollForQuery("$serviceType.local", 12 /* type PTR */))
// TODO: Add another test to check packet reply can trigger serviceFound.
} cleanupStep {
nsdManager.stopServiceDiscovery(discoveryRecord)
discoveryRecord.expectCallback<NsdDiscoveryRecord.DiscoveryEvent.DiscoveryStopped>()
- } cleanupStep {
- maybeStopTapPacketReader(downstreamReader)
- } cleanupStep {
- maybeCloseTestInterface(downstreamIface)
- } cleanup {
- maybeUnregisterTetheringEventCallback(tetheringEventCallback)
}
}
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
index be80787..c71d925 100644
--- a/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.kt
@@ -105,7 +105,6 @@
import com.android.testutils.TestableNetworkAgent
import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnNetworkCreated
import com.android.testutils.TestableNetworkCallback
-import com.android.testutils.assertContainsExactly
import com.android.testutils.assertEmpty
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk30
import com.android.testutils.filters.CtsNetTestCasesMaxTargetSdk33
@@ -128,8 +127,10 @@
import java.util.Random
import java.util.concurrent.Executor
import kotlin.math.min
+import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
@@ -142,10 +143,10 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import kotlin.test.assertNotEquals
private const val TAG = "NsdManagerTest"
private const val TIMEOUT_MS = 2000L
+
// Registration may take a long time if there are devices with the same hostname on the network,
// as the device needs to try another name and probe again. This is especially true since when using
// mdnsresponder the usual hostname is "Android", and on conflict "Android-2", "Android-3", ... are
@@ -187,11 +188,12 @@
private val customHostname = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val customHostname2 = "NsdTestHost%09d".format(Random().nextInt(1_000_000_000))
private val publicKey = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d3")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d3"
+ )
private val handlerThread = HandlerThread(NsdManagerTest::class.java.simpleName)
private val ctsNetUtils by lazy{ CtsNetUtils(context) }
@@ -243,11 +245,14 @@
val iface = tnm.createTapInterface()
val cb = TestableNetworkCallback()
val testNetworkSpecifier = TestNetworkSpecifier(iface.interfaceName)
- cm.requestNetwork(NetworkRequest.Builder()
+ cm.requestNetwork(
+ NetworkRequest.Builder()
.removeCapability(NET_CAPABILITY_TRUSTED)
.addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(testNetworkSpecifier)
- .build(), cb)
+ .build(),
+ cb
+ )
val agent = registerTestNetworkAgent(iface.interfaceName)
val network = agent.network ?: fail("Registered agent should have a network")
@@ -267,12 +272,17 @@
val lp = LinkProperties().apply {
interfaceName = ifaceName
}
- val agent = TestableNetworkAgent(context, handlerThread.looper,
+ val agent = TestableNetworkAgent(
+ context,
+ handlerThread.looper,
NetworkCapabilities().apply {
removeCapability(NET_CAPABILITY_TRUSTED)
addTransportType(TRANSPORT_TEST)
setNetworkSpecifier(TestNetworkSpecifier(ifaceName))
- }, lp, NetworkAgentConfig.Builder().build())
+ },
+ lp,
+ NetworkAgentConfig.Builder().build()
+ )
val network = agent.register()
agent.markConnected()
agent.expectCallback<OnNetworkCreated>()
@@ -346,15 +356,19 @@
Triple(null, null, "null key"),
Triple("", null, "empty key"),
Triple(string256, null, "key with 256 characters"),
- Triple("key", string256.substring(3),
- "key+value combination with more than 255 characters"),
+ Triple(
+ "key",
+ string256.substring(3),
+ "key+value combination with more than 255 characters"
+ ),
Triple("key", string256.substring(4), "key+value combination with 255 characters"),
Triple("\u0019", null, "key with invalid character"),
Triple("=", null, "key with invalid character"),
Triple("\u007f", null, "key with invalid character")
).forEach {
assertFailsWith<IllegalArgumentException>(
- "Setting invalid ${it.third} unexpectedly succeeded") {
+ "Setting invalid ${it.third} unexpectedly succeeded"
+ ) {
si.setAttribute(it.first, it.second)
}
}
@@ -377,7 +391,8 @@
// Test registering without an Executor
nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, registrationRecord)
val registeredInfo = registrationRecord.expectCallback<ServiceRegistered>(
- REGISTRATION_TIMEOUT_MS).serviceInfo
+ REGISTRATION_TIMEOUT_MS
+ ).serviceInfo
// Only service name is included in ServiceRegistered callbacks
assertNull(registeredInfo.serviceType)
@@ -392,7 +407,9 @@
// Expect a service record to be discovered
val foundInfo = discoveryRecord.waitForServiceDiscovered(
- registeredInfo.serviceName, serviceType)
+ registeredInfo.serviceName,
+ serviceType
+ )
// Test resolving without an Executor
val resolveRecord = NsdResolveRecord()
@@ -407,8 +424,10 @@
assertNull(resolvedService.attributes["booleanAttr"])
assertEquals("value", resolvedService.attributes["keyValueAttr"].utf8ToString())
assertEquals("=", resolvedService.attributes["keyEqualsAttr"].utf8ToString())
- assertEquals(" value ",
- resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString())
+ assertEquals(
+ " value ",
+ resolvedService.attributes[" whiteSpaceKeyValueAttr "].utf8ToString()
+ )
assertEquals(string256.substring(9), resolvedService.attributes["longkey"].utf8ToString())
assertArrayEquals(testByteArray, resolvedService.attributes["binaryDataAttr"])
assertTrue(resolvedService.attributes.containsKey("nullBinaryDataAttr"))
@@ -442,12 +461,15 @@
val registrationRecord2 = NsdRegistrationRecord()
nsdManager.registerService(si2, NsdManager.PROTOCOL_DNS_SD, registrationRecord2)
val registeredInfo2 = registrationRecord2.expectCallback<ServiceRegistered>(
- REGISTRATION_TIMEOUT_MS).serviceInfo
+ REGISTRATION_TIMEOUT_MS
+ ).serviceInfo
// Expect a service record to be discovered (and filter the ones
// that are unrelated to this test)
val foundInfo2 = discoveryRecord.waitForServiceDiscovered(
- registeredInfo2.serviceName, serviceType)
+ registeredInfo2.serviceName,
+ serviceType
+ )
// Resolve the service
val resolveRecord2 = NsdResolveRecord()
@@ -950,7 +972,8 @@
// when the compat change is disabled.
// Note that before T the compat constant had a different int value.
assertFalse(CompatChanges.isChangeEnabled(
- ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER))
+ ConnectivityCompatChanges.RUN_NATIVE_NSD_ONLY_IF_LEGACY_APPS_T_AND_LATER
+ ))
}
@Test
@@ -1023,7 +1046,9 @@
// onStopResolutionFailed on the record directly then verify it is received.
val resolveRecord = NsdResolveRecord()
resolveRecord.onStopResolutionFailed(
- NsdServiceInfo(), NsdManager.FAILURE_OPERATION_NOT_RUNNING)
+ NsdServiceInfo(),
+ NsdManager.FAILURE_OPERATION_NOT_RUNNING
+ )
val failedCb = resolveRecord.expectCallback<StopResolutionFailed>()
assertEquals(NsdManager.FAILURE_OPERATION_NOT_RUNNING, failedCb.errorCode)
}
@@ -1274,15 +1299,22 @@
val si = makeTestServiceInfo(testNetwork1.network)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1313,15 +1345,22 @@
parseNumericAddress("2001:db8::3"))
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1352,15 +1391,22 @@
hostname = customHostname
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
// Register service on testNetwork1
val registrationRecord = NsdRegistrationRecord()
- nsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, { it.run() },
- registrationRecord)
+ nsdManager.registerService(
+ si,
+ NsdManager.PROTOCOL_DNS_SD,
+ { it.run() },
+ registrationRecord
+ )
tryTest {
assertNotNull(packetReader.pollForProbe(serviceName, serviceType),
@@ -1392,8 +1438,11 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1471,7 +1520,9 @@
val registeredService = registerService(registrationRecord, si)
val packetReader = TapPacketReader(
Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1501,11 +1552,11 @@
val newRegistration =
registrationRecord
.expectCallbackEventually<ServiceRegistered>(REGISTRATION_TIMEOUT_MS) {
- it.serviceInfo.serviceName == serviceName
- && it.serviceInfo.hostname.let { hostname ->
- hostname != null
- && hostname.startsWith(customHostname)
- && hostname != customHostname
+ it.serviceInfo.serviceName == serviceName &&
+ it.serviceInfo.hostname.let { hostname ->
+ hostname != null &&
+ hostname.startsWith(customHostname) &&
+ hostname != customHostname
}
}
@@ -1536,8 +1587,11 @@
val registrationRecord = NsdRegistrationRecord()
val discoveryRecord = NsdDiscoveryRecord()
val registeredService = registerService(registrationRecord, si)
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1576,13 +1630,21 @@
fun testDiscoveryWithPtrOnlyResponse_ServiceIsFound() {
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
- nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord)
+ nsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord
+ )
tryTest {
discoveryRecord.expectCallback<DiscoveryStarted>()
@@ -1626,8 +1688,10 @@
fun testResolveWhenServerSendsNoAdditionalRecord() {
// Resolve service on testNetwork1
val resolveRecord = NsdResolveRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
)
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -1651,16 +1715,21 @@
rdata='testkey=testvalue')
))).hex()
*/
- val srvTxtResponsePayload = HexDump.hexStringToByteArray("000084000000000200000000104" +
+ val srvTxtResponsePayload = HexDump.hexStringToByteArray(
+ "000084000000000200000000104" +
"e7364546573743132333435363738390d5f6e6d74313233343536373839045f746370056c6f6" +
"3616c0000218001000000780011000000007a020874657374686f7374c030c00c00100001000" +
- "00078001211746573746b65793d7465737476616c7565")
+ "00078001211746573746b65793d7465737476616c7565"
+ )
replaceServiceNameAndTypeWithTestSuffix(srvTxtResponsePayload)
packetReader.sendResponse(buildMdnsPacket(srvTxtResponsePayload))
val testHostname = "testhost.local"
- val addressQuery = packetReader.pollForQuery(testHostname,
- DnsResolver.TYPE_A, DnsResolver.TYPE_AAAA)
+ val addressQuery = packetReader.pollForQuery(
+ testHostname,
+ DnsResolver.TYPE_A,
+ DnsResolver.TYPE_AAAA
+ )
assertNotNull(addressQuery)
/*
@@ -1672,9 +1741,11 @@
rdata='2001:db8::123')
))).hex()
*/
- val addressPayload = HexDump.hexStringToByteArray("0000840000000002000000000874657374" +
+ val addressPayload = HexDump.hexStringToByteArray(
+ "0000840000000002000000000874657374" +
"686f7374056c6f63616c0000010001000000780004c000027bc00c001c000100000078001020" +
- "010db8000000000000000000000123")
+ "010db8000000000000000000000123"
+ )
packetReader.sendResponse(buildMdnsPacket(addressPayload))
val serviceResolved = resolveRecord.expectCallback<ServiceResolved>()
@@ -1688,7 +1759,8 @@
}
assertEquals(
setOf(parseNumericAddress("192.0.2.123"), parseNumericAddress("2001:db8::123")),
- serviceResolved.serviceInfo.hostAddresses.toSet())
+ serviceResolved.serviceInfo.hostAddresses.toSet()
+ )
}
@Test
@@ -1715,7 +1787,7 @@
scapy.raw(scapy.DNS(rd=0, qr=0, aa=0, qd =
scapy.DNSQR(qname='_nmt123456789._tcp.local', qtype='PTR', qclass=0x8001)
)).hex()
- */
+ */
val mdnsPayload = HexDump.hexStringToByteArray("0000000000010000000000000d5f6e6d74313" +
"233343536373839045f746370056c6f63616c00000c8001")
replaceServiceNameAndTypeWithTestSuffix(mdnsPayload)
@@ -1771,7 +1843,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val query = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d74313233343" +
"536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
"d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d743132333" +
@@ -1806,7 +1878,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=2150,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val query2 = HexDump.hexStringToByteArray("0000000000020001000000000d5f6e6d7431323334" +
"3536373839045f746370056c6f63616c00000c8001104e736454657374313233343536373839" +
"0d5f6e6d74313233343536373839045f746370056c6f63616c00001080010d5f6e6d74313233" +
@@ -1858,7 +1930,7 @@
scapy.DNSQR(qname='NsdTest123456789._nmt123456789._tcp.local', qtype='TXT',
qclass=0x8001)
)).hex()
- */
+ */
val query = HexDump.hexStringToByteArray("0000020000020000000000000d5f6e6d74313233343" +
"536373839045f746370056c6f63616c00000c8001104e7364546573743132333435363738390" +
"d5f6e6d74313233343536373839045f746370056c6f63616c0000108001")
@@ -1870,7 +1942,7 @@
an = scapy.DNSRR(rrname='_test._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest._test._tcp.local')
)).hex()
- */
+ */
val knownAnswer1 = HexDump.hexStringToByteArray("000002000000000100000000055f74657374" +
"045f746370056c6f63616c00000c000100001194001a074e736454657374055f74657374045f" +
"746370056c6f63616c00")
@@ -1882,7 +1954,7 @@
an = scapy.DNSRR(rrname='_nmt123456789._tcp.local', type='PTR', ttl=4500,
rdata='NsdTest123456789._nmt123456789._tcp.local')
)).hex()
- */
+ */
val knownAnswer2 = HexDump.hexStringToByteArray("0000000000000001000000000d5f6e6d7431" +
"3233343536373839045f746370056c6f63616c00000c000100001194002b104e736454657374" +
"3132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00")
@@ -1919,13 +1991,21 @@
// Register service on testNetwork1
val discoveryRecord = NsdDiscoveryRecord()
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
- nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord)
+ nsdManager.discoverServices(
+ serviceType,
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord
+ )
tryTest {
discoveryRecord.expectCallback<DiscoveryStarted>()
@@ -1987,10 +2067,12 @@
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val hostAddresses2 = listOf(
parseNumericAddress("192.0.2.24"),
- parseNumericAddress("2001:db8::3"))
+ parseNumericAddress("2001:db8::3")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.serviceName = serviceName
@@ -2054,10 +2136,12 @@
val hostAddresses1 = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val hostAddresses2 = listOf(
parseNumericAddress("192.0.2.24"),
- parseNumericAddress("2001:db8::3"))
+ parseNumericAddress("2001:db8::3")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.hostname = customHostname
@@ -2105,7 +2189,8 @@
val hostAddresses = listOf(
parseNumericAddress("192.0.2.23"),
parseNumericAddress("2001:db8::1"),
- parseNumericAddress("2001:db8::2"))
+ parseNumericAddress("2001:db8::2")
+ )
val si1 = NsdServiceInfo().also {
it.network = testNetwork1.network
it.serviceType = serviceType
@@ -2270,8 +2355,11 @@
it.port = TEST_PORT
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2322,8 +2410,11 @@
parseNumericAddress("2001:db8::2"))
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2376,8 +2467,11 @@
it.hostAddresses = listOf()
it.publicKey = publicKey
}
- val packetReader = TapPacketReader(Handler(handlerThread.looper),
- testNetwork1.iface.fileDescriptor.fileDescriptor, 1500 /* maxPacketSize */)
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
packetReader.startAsyncForTest()
handlerThread.waitForIdle(TIMEOUT_MS)
@@ -2430,10 +2524,20 @@
val discoveryRecord1 = NsdDiscoveryRecord()
val discoveryRecord2 = NsdDiscoveryRecord()
val discoveryRecord3 = NsdDiscoveryRecord()
- nsdManager.discoverServices("_test1._tcp", NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord1)
- nsdManager.discoverServices("_test2._tcp", NsdManager.PROTOCOL_DNS_SD,
- testNetwork1.network, { it.run() }, discoveryRecord2)
+ nsdManager.discoverServices(
+ "_test1._tcp",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord1
+ )
+ nsdManager.discoverServices(
+ "_test2._tcp",
+ NsdManager.PROTOCOL_DNS_SD,
+ testNetwork1.network,
+ { it.run() },
+ discoveryRecord2
+ )
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, discoveryRecord3)
tryTest {
@@ -2471,6 +2575,44 @@
}
}
+ @Test
+ fun testAdvertiseServiceWithNoAttributes_TxtRecordIstNotEmpty() {
+ deviceConfigRule.setConfig(
+ NAMESPACE_TETHERING,
+ "test_nsd_avoid_advertising_empty_txt_records",
+ "1"
+ )
+ val packetReader = TapPacketReader(
+ Handler(handlerThread.looper),
+ testNetwork1.iface.fileDescriptor.fileDescriptor,
+ 1500 /* maxPacketSize */
+ )
+ packetReader.startAsyncForTest()
+ handlerThread.waitForIdle(TIMEOUT_MS)
+
+ // Test behavior described in RFC6763 6.1: empty TXT records are not allowed, but TXT
+ // records with a zero length string are equivalent.
+ val si = makeTestServiceInfo(testNetwork1.network)
+ // Register service on testNetwork1
+ val registrationRecord = NsdRegistrationRecord()
+ registerService(registrationRecord, si)
+
+ tryTest {
+ val announcement =
+ packetReader.pollForReply("$serviceName.$serviceType.local", DnsResolver.TYPE_TXT)
+ assertNotNull(announcement)
+ val txtRecords = announcement.records[ANSECTION].filter {
+ it.nsType == DnsResolver.TYPE_TXT
+ }
+ assertEquals(1, txtRecords.size)
+ // The TXT record should contain as single zero
+ assertContentEquals(byteArrayOf(0), txtRecords[0].rr)
+ } cleanup {
+ nsdManager.unregisterService(registrationRecord)
+ registrationRecord.expectCallback<ServiceUnregistered>()
+ }
+ }
+
private fun hasServiceTypeClientsForNetwork(clients: List<String>, network: Network): Boolean {
return clients.any { client -> client.substring(
client.indexOf("network=") + "network=".length,
@@ -2489,11 +2631,16 @@
*/
private fun getServiceTypeClients(): List<String> {
return SystemUtil.runShellCommand(
- InstrumentationRegistry.getInstrumentation(), "dumpsys servicediscovery")
+ InstrumentationRegistry.getInstrumentation(),
+ "dumpsys servicediscovery"
+ )
.split("\n").mapNotNull { line ->
line.indexOf("ServiceTypeClient:").let { idx ->
- if (idx == -1) null
- else line.substring(idx)
+ if (idx == -1) {
+ null
+ } else {
+ line.substring(idx)
+ }
}
}
}
@@ -2506,9 +2653,11 @@
rclass=0x8001, port=31234, target='conflict.local', ttl=120)
)).hex()
*/
- val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000104e736454657" +
+ val mdnsPayload = HexDump.hexStringToByteArray(
+ "000084000000000100000000104e736454657" +
"3743132333435363738390d5f6e6d74313233343536373839045f746370056c6f63616c00002" +
- "18001000000780016000000007a0208636f6e666c696374056c6f63616c00")
+ "18001000000780016000000007a0208636f6e666c696374056c6f63616c00"
+ )
replaceServiceNameAndTypeWithTestSuffix(mdnsPayload)
return buildMdnsPacket(mdnsPayload)
@@ -2522,9 +2671,11 @@
rdata='2001:db8::321')
)).hex()
*/
- val mdnsPayload = HexDump.hexStringToByteArray("000084000000000100000000144e7364" +
+ val mdnsPayload = HexDump.hexStringToByteArray(
+ "000084000000000100000000144e7364" +
"54657374486f7374313233343536373839056c6f63616c00001c000100000078001020010db80000" +
- "00000000000000000321")
+ "00000000000000000321"
+ )
replaceCustomHostnameWithTestSuffix(mdnsPayload)
return buildMdnsPacket(mdnsPayload)
@@ -2575,22 +2726,29 @@
mdnsPayload: ByteArray,
srcAddr: Inet6Address = testSrcAddr
): ByteBuffer {
- val packetBuffer = PacketBuilder.allocate(true /* hasEther */, IPPROTO_IPV6,
- IPPROTO_UDP, mdnsPayload.size)
+ val packetBuffer = PacketBuilder.allocate(
+ true /* hasEther */,
+ IPPROTO_IPV6,
+ IPPROTO_UDP,
+ mdnsPayload.size
+ )
val packetBuilder = PacketBuilder(packetBuffer)
// Multicast ethernet address for IPv6 to ff02::fb
val multicastEthAddr = MacAddress.fromBytes(
- byteArrayOf(0x33, 0x33, 0, 0, 0, 0xfb.toByte()))
+ byteArrayOf(0x33, 0x33, 0, 0, 0, 0xfb.toByte())
+ )
packetBuilder.writeL2Header(
MacAddress.fromBytes(byteArrayOf(1, 2, 3, 4, 5, 6)) /* srcMac */,
multicastEthAddr,
- ETH_P_IPV6.toShort())
+ ETH_P_IPV6.toShort()
+ )
packetBuilder.writeIpv6Header(
0x60000000, // version=6, traffic class=0x0, flowlabel=0x0
IPPROTO_UDP.toByte(),
64 /* hop limit */,
srcAddr,
- multicastIpv6Addr /* dstIp */)
+ multicastIpv6Addr /* dstIp */
+ )
packetBuilder.writeUdpHeader(MDNS_PORT /* srcPort */, MDNS_PORT /* dstPort */)
packetBuffer.put(mdnsPayload)
return packetBuilder.finalizePacket()
diff --git a/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
new file mode 100644
index 0000000..32d6899
--- /dev/null
+++ b/tests/cts/net/util/java/android/net/cts/util/EthernetTestInterface.kt
@@ -0,0 +1,128 @@
+/*
+ * 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.net.cts.util
+
+import android.Manifest.permission.NETWORK_SETTINGS
+import android.content.Context
+import android.net.EthernetManager
+import android.net.EthernetManager.InterfaceStateListener
+import android.net.EthernetManager.STATE_ABSENT
+import android.net.EthernetManager.STATE_LINK_UP
+import android.net.IpConfiguration
+import android.net.TestNetworkInterface
+import android.net.cts.util.EthernetTestInterface.EthernetStateListener.CallbackEntry.InterfaceStateChanged
+import android.os.Handler
+import android.util.Log
+import com.android.net.module.util.ArrayTrackRecord
+import com.android.testutils.TapPacketReader
+import com.android.testutils.runAsShell
+import com.android.testutils.waitForIdle
+import java.net.NetworkInterface
+import kotlin.concurrent.Volatile
+import kotlin.test.assertNotNull
+
+private const val TAG = "EthernetTestInterface"
+private const val TIMEOUT_MS = 5_000L
+
+/**
+ * A class to manage the lifecycle of an ethernet interface.
+ *
+ * This class encapsulates creating new tun/tap interfaces and registering them with ethernet
+ * service.
+ */
+class EthernetTestInterface(
+ private val context: Context,
+ private val handler: Handler,
+ val testIface: TestNetworkInterface
+) {
+ private class EthernetStateListener(private val trackedIface: String) : InterfaceStateListener {
+ val events = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+ sealed class CallbackEntry {
+ data class InterfaceStateChanged(
+ val iface: String,
+ val state: Int,
+ val role: Int,
+ val cfg: IpConfiguration?
+ ) : CallbackEntry()
+ }
+
+ override fun onInterfaceStateChanged(
+ iface: String,
+ state: Int,
+ role: Int,
+ cfg: IpConfiguration?
+ ) {
+ // filter out callbacks for other interfaces
+ if (iface != trackedIface) return
+ events.add(InterfaceStateChanged(iface, state, role, cfg))
+ }
+
+ fun eventuallyExpect(state: Int) {
+ val cb = events.poll(TIMEOUT_MS) { it is InterfaceStateChanged && it.state == state }
+ assertNotNull(cb, "Never received state $state. Got: ${events.backtrace()}")
+ }
+ }
+
+ val name get() = testIface.interfaceName
+ val mtu: Int
+ get() {
+ val nif = NetworkInterface.getByName(name)
+ assertNotNull(nif)
+ return nif.mtu
+ }
+ val packetReader = TapPacketReader(handler, testIface.fileDescriptor.fileDescriptor, mtu)
+ private val listener = EthernetStateListener(name)
+ private val em = context.getSystemService(EthernetManager::class.java)!!
+ @Volatile private var cleanedUp = false
+
+ init{
+ em.addInterfaceStateListener(handler::post, listener)
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(true)
+ }
+ // Wait for link up to be processed in EthernetManager before returning.
+ listener.eventuallyExpect(STATE_LINK_UP)
+ handler.post { packetReader.start() }
+ handler.waitForIdle(TIMEOUT_MS)
+ }
+
+ fun destroy() {
+ // packetReader.stop() closes the test interface.
+ handler.post { packetReader.stop() }
+ handler.waitForIdle(TIMEOUT_MS)
+ listener.eventuallyExpect(STATE_ABSENT)
+
+ // setIncludeTestInterfaces() posts on the handler and does not run synchronously. However,
+ // there should be no need for a synchronization mechanism here. If the next test is
+ // bringing up its interface, a RTM_NEWLINK will be put on the back of the handler and is
+ // guaranteed to be in order with (i.e. after) this call, so there is no race here.
+ runAsShell(NETWORK_SETTINGS) {
+ em.setIncludeTestInterfaces(false)
+ }
+ em.removeInterfaceStateListener(listener)
+
+ cleanedUp = true
+ }
+
+ protected fun finalize() {
+ if (!cleanedUp) {
+ Log.wtf(TAG, "destroy() was not called for interface $name.")
+ destroy()
+ }
+ }
+}
diff --git a/tests/native/connectivity_native_test/Android.bp b/tests/native/connectivity_native_test/Android.bp
index ab2f28c..39a08fa 100644
--- a/tests/native/connectivity_native_test/Android.bp
+++ b/tests/native/connectivity_native_test/Android.bp
@@ -21,6 +21,7 @@
stl: "libc++_static",
shared_libs: [
"libbinder_ndk",
+ "libcom.android.tethering.connectivity_native",
"liblog",
"libnetutils",
"libprocessgroup",
diff --git a/tests/native/connectivity_native_test/AndroidTestTemplate.xml b/tests/native/connectivity_native_test/AndroidTestTemplate.xml
index 44e35a9..4ea114c 100644
--- a/tests/native/connectivity_native_test/AndroidTestTemplate.xml
+++ b/tests/native/connectivity_native_test/AndroidTestTemplate.xml
@@ -15,8 +15,10 @@
<configuration description="Configuration for connectivity {MODULE} tests">
<option name="test-suite-tag" value="mts" />
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
- <!-- The tested code is only part of a SDK 30+ module (Tethering) -->
- <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
+ <!-- The tested library is only usable on U+ -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk34ModuleController" />
+ <!-- The tested library is only available with the primary abi -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
diff --git a/tests/native/connectivity_native_test/connectivity_native_test.cpp b/tests/native/connectivity_native_test/connectivity_native_test.cpp
index f62a30b..faaf150 100644
--- a/tests/native/connectivity_native_test/connectivity_native_test.cpp
+++ b/tests/native/connectivity_native_test/connectivity_native_test.cpp
@@ -14,25 +14,17 @@
* limitations under the License.
*/
+#include <android-modules-utils/sdk_level.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
-#include <android-modules-utils/sdk_level.h>
-#include <cutils/misc.h> // FIRST_APPLICATION_UID
+#include <connectivity_native.h>
+#include <cutils/misc.h> // FIRST_APPLICATION_UID
#include <dlfcn.h>
#include <gtest/gtest.h>
#include <netinet/in.h>
#include "bpf/BpfUtils.h"
-typedef int (*GetPortsBlockedForBind)(in_port_t*, size_t*);
-GetPortsBlockedForBind getPortsBlockedForBind;
-typedef int (*BlockPortForBind)(in_port_t);
-BlockPortForBind blockPortForBind;
-typedef int (*UnblockPortForBind)(in_port_t);
-UnblockPortForBind unblockPortForBind;
-typedef int (*UnblockAllPortsForBind)();
-UnblockAllPortsForBind unblockAllPortsForBind;
-
class ConnectivityNativeBinderTest : public ::testing::Test {
public:
in_port_t mActualBlockedPorts[65535];
@@ -50,29 +42,12 @@
if (!android::bpf::isAtLeastKernelVersion(5, 4, 0))
GTEST_SKIP() << "Kernel should be at least 5.4.";
- // Necessary to use dlopen/dlsym since the lib is only available on U and there
- // is no Sdk34ModuleController in tradefed yet.
- // TODO: link against the library directly and add Sdk34ModuleController to
- // AndroidTest.txml when available.
- void* nativeLib = dlopen("libcom.android.tethering.connectivity_native.so", RTLD_NOW);
- ASSERT_NE(nullptr, nativeLib);
- getPortsBlockedForBind = reinterpret_cast<GetPortsBlockedForBind>(
- dlsym(nativeLib, "AConnectivityNative_getPortsBlockedForBind"));
- ASSERT_NE(nullptr, getPortsBlockedForBind);
- blockPortForBind = reinterpret_cast<BlockPortForBind>(
- dlsym(nativeLib, "AConnectivityNative_blockPortForBind"));
- ASSERT_NE(nullptr, blockPortForBind);
- unblockPortForBind = reinterpret_cast<UnblockPortForBind>(
- dlsym(nativeLib, "AConnectivityNative_unblockPortForBind"));
- ASSERT_NE(nullptr, unblockPortForBind);
- unblockAllPortsForBind = reinterpret_cast<UnblockAllPortsForBind>(
- dlsym(nativeLib, "AConnectivityNative_unblockAllPortsForBind"));
- ASSERT_NE(nullptr, unblockAllPortsForBind);
-
- // If there are already ports being blocked on device unblockAllPortsForBind() store
- // the currently blocked ports and add them back at the end of the test. Do this for
- // every test case so additional test cases do not forget to add ports back.
- int err = getPortsBlockedForBind(mActualBlockedPorts, &mActualBlockedPortsCount);
+ // If there are already ports being blocked on device store the
+ // currently blocked ports and add them back at the end of the test. Do
+ // this for every test case so additional test cases do not forget to
+ // add ports back.
+ int err = AConnectivityNative_getPortsBlockedForBind(
+ mActualBlockedPorts, &mActualBlockedPortsCount);
EXPECT_EQ(err, 0);
restoreBlockedPorts = true;
}
@@ -81,8 +56,9 @@
int err;
if (mActualBlockedPortsCount > 0 && restoreBlockedPorts) {
for (int i=0; i < mActualBlockedPortsCount; i++) {
- err = blockPortForBind(mActualBlockedPorts[i]);
- EXPECT_EQ(err, 0);
+ err =
+ AConnectivityNative_blockPortForBind(mActualBlockedPorts[i]);
+ EXPECT_EQ(err, 0);
}
}
}
@@ -99,7 +75,7 @@
int blockedPort = 0;
if (blockPort) {
blockedPort = ntohs(port);
- err = blockPortForBind(blockedPort);
+ err = AConnectivityNative_blockPortForBind(blockedPort);
EXPECT_EQ(err, 0);
}
@@ -107,7 +83,7 @@
if (blockPort) {
EXPECT_EQ(-1, sock3);
- err = unblockPortForBind(blockedPort);
+ err = AConnectivityNative_unblockPortForBind(blockedPort);
EXPECT_EQ(err, 0);
} else {
EXPECT_NE(-1, sock3);
@@ -197,11 +173,11 @@
}
TEST_F(ConnectivityNativeBinderTest, BlockPortTwice) {
- int err = blockPortForBind(5555);
+ int err = AConnectivityNative_blockPortForBind(5555);
EXPECT_EQ(err, 0);
- err = blockPortForBind(5555);
+ err = AConnectivityNative_blockPortForBind(5555);
EXPECT_EQ(err, 0);
- err = unblockPortForBind(5555);
+ err = AConnectivityNative_unblockPortForBind(5555);
EXPECT_EQ(err, 0);
}
@@ -210,16 +186,17 @@
in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
if (mActualBlockedPortsCount > 0) {
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
}
for (int i : blockedPorts) {
- err = blockPortForBind(i);
+ err = AConnectivityNative_blockPortForBind(i);
EXPECT_EQ(err, 0);
}
size_t actualBlockedPortsCount = 8;
in_port_t actualBlockedPorts[actualBlockedPortsCount];
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_NE(actualBlockedPortsCount, 0);
for (int i=0; i < actualBlockedPortsCount; i++) {
@@ -227,9 +204,10 @@
}
// Remove the ports we added.
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
EXPECT_EQ(err, 0);
- err = getPortsBlockedForBind(actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(actualBlockedPorts,
+ &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 0);
}
@@ -239,23 +217,25 @@
in_port_t blockedPorts[8] = {1, 100, 1220, 1333, 2700, 5555, 5600, 65000};
if (mActualBlockedPortsCount > 0) {
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
}
for (int i : blockedPorts) {
- err = blockPortForBind(i);
+ err = AConnectivityNative_blockPortForBind(i);
EXPECT_EQ(err, 0);
}
size_t actualBlockedPortsCount = 8;
in_port_t actualBlockedPorts[actualBlockedPortsCount];
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 8);
- err = unblockAllPortsForBind();
+ err = AConnectivityNative_unblockAllPortsForBind();
EXPECT_EQ(err, 0);
- err = getPortsBlockedForBind((in_port_t*) actualBlockedPorts, &actualBlockedPortsCount);
+ err = AConnectivityNative_getPortsBlockedForBind(
+ (in_port_t *)actualBlockedPorts, &actualBlockedPortsCount);
EXPECT_EQ(err, 0);
EXPECT_EQ(actualBlockedPortsCount, 0);
// If mActualBlockedPorts is not empty, ports will be added back in teardown.
@@ -264,7 +244,7 @@
TEST_F(ConnectivityNativeBinderTest, CheckPermission) {
int curUid = getuid();
EXPECT_EQ(0, seteuid(FIRST_APPLICATION_UID + 2000)) << "seteuid failed: " << strerror(errno);
- int err = blockPortForBind((in_port_t) 5555);
+ int err = AConnectivityNative_blockPortForBind((in_port_t)5555);
EXPECT_EQ(EPERM, err);
EXPECT_EQ(0, seteuid(curUid)) << "seteuid failed: " << strerror(errno);
}
diff --git a/tests/unit/java/com/android/server/BpfNetMapsTest.java b/tests/unit/java/com/android/server/BpfNetMapsTest.java
index 859c54a..c1c15ca 100644
--- a/tests/unit/java/com/android/server/BpfNetMapsTest.java
+++ b/tests/unit/java/com/android/server/BpfNetMapsTest.java
@@ -41,6 +41,7 @@
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_ADMIN_DISABLED;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER;
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED;
+import static android.net.ConnectivityManager.BLOCKED_REASON_APP_BACKGROUND;
import static android.net.ConnectivityManager.BLOCKED_REASON_APP_STANDBY;
import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER;
import static android.net.ConnectivityManager.BLOCKED_REASON_DOZE;
@@ -136,6 +137,12 @@
private static final int TEST_UID = 10086;
private static final int[] TEST_UIDS = {10002, 10003};
+ private static final int[] CORE_AIDS = {
+ Process.ROOT_UID,
+ Process.SYSTEM_UID,
+ Process.FIRST_APPLICATION_UID - 10,
+ Process.FIRST_APPLICATION_UID - 1,
+ };
private static final String TEST_IF_NAME = "wlan0";
private static final int TEST_IF_INDEX = 7;
private static final int NO_IIF = 0;
@@ -1261,15 +1268,9 @@
assertTrue(BpfNetMapsUtils.isUidNetworkingBlocked(TEST_UID, false, mConfigurationMap,
mUidOwnerMap, mDataSaverEnabledMap));
- final int[] coreAids = new int[] {
- Process.ROOT_UID,
- Process.SYSTEM_UID,
- Process.FIRST_APPLICATION_UID - 10,
- Process.FIRST_APPLICATION_UID - 1,
- };
// Core appIds are not on the chain but should still be allowed on any user.
for (int userId = 0; userId < 20; userId++) {
- for (final int aid : coreAids) {
+ for (final int aid : CORE_AIDS) {
final int uid = UserHandle.getUid(userId, aid);
assertFalse(BpfNetMapsUtils.isUidNetworkingBlocked(uid, false, mConfigurationMap,
mUidOwnerMap, mDataSaverEnabledMap));
@@ -1277,6 +1278,26 @@
}
}
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.S_V2)
+ public void testGetUidNetworkingBlockedReasonsForCoreUids() throws Exception {
+ // Enable BACKGROUND_MATCH that is an allowlist match.
+ mConfigurationMap.updateEntry(UID_RULES_CONFIGURATION_KEY, new U32(BACKGROUND_MATCH));
+
+ // Non-core uid that is not on this chain is blocked by BLOCKED_REASON_APP_BACKGROUND.
+ assertEquals(BLOCKED_REASON_APP_BACKGROUND, BpfNetMapsUtils.getUidNetworkingBlockedReasons(
+ TEST_UID, mConfigurationMap, mUidOwnerMap, mDataSaverEnabledMap));
+
+ // Core appIds are not on the chain but should not be blocked on any users.
+ for (int userId = 0; userId < 20; userId++) {
+ for (final int aid : CORE_AIDS) {
+ final int uid = UserHandle.getUid(userId, aid);
+ assertEquals(BLOCKED_REASON_NONE, BpfNetMapsUtils.getUidNetworkingBlockedReasons(
+ uid, mConfigurationMap, mUidOwnerMap, mDataSaverEnabledMap));
+ }
+ }
+ }
+
private void doTestIsUidRestrictedOnMeteredNetworks(
final long enabledMatches,
final long uidRules,
diff --git a/tests/unit/java/com/android/server/CallbackQueueTest.kt b/tests/unit/java/com/android/server/CallbackQueueTest.kt
index a6dd5c3..d8d35c1 100644
--- a/tests/unit/java/com/android/server/CallbackQueueTest.kt
+++ b/tests/unit/java/com/android/server/CallbackQueueTest.kt
@@ -116,7 +116,7 @@
addCallback(TEST_NETID_1, CALLBACK_AVAILABLE)
addCallback(TEST_NETID_2, CALLBACK_AVAILABLE)
}
- val queue2 = CallbackQueue(queue1.shrinkedBackingArray)
+ val queue2 = CallbackQueue(queue1.minimizedBackingArray)
assertQueueEquals(listOf(
TEST_NETID_1 to CALLBACK_AVAILABLE,
TEST_NETID_2 to CALLBACK_AVAILABLE
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
index 2cb97c9..9674da3 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordRepositoryTest.kt
@@ -123,18 +123,18 @@
}
private val TEST_PUBLIC_KEY = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d3")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d3")
private val TEST_PUBLIC_KEY_2 = hexStringToByteArray(
- "0201030dc141d0637960b98cbc12cfca"
- + "221d2879dac26ee5b460e9007c992e19"
- + "02d897c391b03764d448f7d0c772fdb0"
- + "3b1d9d6d52ff8886769e8e2362513565"
- + "270962d4")
+ "0201030dc141d0637960b98cbc12cfca" +
+ "221d2879dac26ee5b460e9007c992e19" +
+ "02d897c391b03764d448f7d0c772fdb0" +
+ "3b1d9d6d52ff8886769e8e2362513565" +
+ "270962d4")
@RunWith(DevSdkIgnoreRunner::class)
@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.S_V2)
@@ -159,7 +159,7 @@
@Before
fun setUp() {
- deps.resetElapsedRealTime();
+ deps.resetElapsedRealTime()
thread.start()
}
@@ -172,11 +172,13 @@
private fun makeFlags(
includeInetAddressesInProbing: Boolean = false,
isKnownAnswerSuppressionEnabled: Boolean = false,
- unicastReplyEnabled: Boolean = true
+ unicastReplyEnabled: Boolean = true,
+ avoidAdvertisingEmptyTxtRecords: Boolean = true
) = MdnsFeatureFlags.Builder()
.setIncludeInetAddressRecordsInProbing(includeInetAddressesInProbing)
.setIsKnownAnswerSuppressionEnabled(isKnownAnswerSuppressionEnabled)
.setIsUnicastReplyEnabled(unicastReplyEnabled)
+ .setAvoidAdvertisingEmptyTxtRecords(avoidAdvertisingEmptyTxtRecords)
.build()
@Test
@@ -1721,6 +1723,30 @@
}
@Test
+ fun testGetConflictingServices_ZeroLengthTxtRecord_NoConflict() {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+ val packet = MdnsPacket(
+ 0 /* flags */,
+ emptyList() /* questions */,
+ listOf(
+ MdnsTextRecord(
+ arrayOf("MyOtherTestService", "_testservice", "_tcp", "local"),
+ 0L /* receiptTimeMillis */,
+ true /* cacheFlush */,
+ SHORT_TTL,
+ listOf(TextEntry("", null as ByteArray?))
+ ),
+ ) /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */
+ )
+
+ assertEquals(emptyMap(), repository.getConflictingServices(packet))
+ }
+
+ @Test
fun testGetServiceRepliedRequestsCount() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
repository.initWithService(TEST_SERVICE_ID_1, TEST_SERVICE_1)
@@ -2168,6 +2194,46 @@
assertEquals(knownAnswers, reply.knownAnswers)
}
+ private fun doAddServiceWithEmptyTxtRecordTest(flags: MdnsFeatureFlags): MdnsTextRecord {
+ val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, flags)
+ repository.addServiceAndFinishProbing(TEST_SERVICE_ID_1, TEST_SERVICE_1)
+
+ val questions = listOf(MdnsTextRecord(
+ arrayOf("MyTestService", "_testservice", "_tcp", "local"),
+ true /* isUnicast */
+ ))
+ val query = MdnsPacket(
+ 0 /* flags */,
+ questions,
+ emptyList() /* answers */,
+ emptyList() /* authorityRecords */,
+ emptyList() /* additionalRecords */
+ )
+ val src = InetSocketAddress(parseNumericAddress("192.0.2.123"), 5353)
+ val reply = repository.getReply(query, src)
+
+ assertNotNull(reply)
+ assertEquals(1, reply.answers.size)
+ assertTrue(reply.answers[0] is MdnsTextRecord)
+ return reply.answers[0] as MdnsTextRecord
+ }
+
+ @Test
+ fun testAddService_AvoidEmptyTxtRecords_HasTxtRecordWithEmptyString() {
+ val answerRecord = doAddServiceWithEmptyTxtRecordTest(makeFlags())
+ assertEquals(1, answerRecord.entries.size)
+ assertEquals(0, answerRecord.entries[0].key.length)
+ assertNull(answerRecord.entries[0].value)
+ }
+
+ @Test
+ fun testAddService_UsesEmptyTxtRecords_HasEmptyTxtRecord() {
+ val answerRecord = doAddServiceWithEmptyTxtRecordTest(makeFlags(
+ avoidAdvertisingEmptyTxtRecords = false
+ ))
+ assertEquals(0, answerRecord.entries.size)
+ }
+
@Test
fun testRestartProbingForHostname() {
val repository = MdnsRecordRepository(thread.looper, deps, TEST_HOSTNAME, makeFlags())
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
index 63548c1..784c502 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsRecordTests.java
@@ -28,6 +28,8 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
+import static java.util.Collections.emptyList;
+
import android.util.Log;
import com.android.net.module.util.HexDump;
@@ -372,6 +374,30 @@
assertEquals(dataInText, dataOutText);
}
+ private static MdnsTextRecord makeTextRecordWithEntries(List<TextEntry> entries) {
+ return new MdnsTextRecord(new String[] { "test", "record" }, 0L /* receiptTimeMillis */,
+ true /* cacheFlush */, 120_000L /* ttlMillis */, entries);
+ }
+
+ @Test
+ public void testTextRecord_EmptyRecordsAreEquivalent() {
+ final MdnsTextRecord record1 = makeTextRecordWithEntries(emptyList());
+ final MdnsTextRecord record2 = makeTextRecordWithEntries(
+ List.of(new TextEntry("", (byte[]) null)));
+ final MdnsTextRecord record3 = makeTextRecordWithEntries(
+ List.of(new TextEntry(null, (byte[]) null)));
+ final MdnsTextRecord nonEmptyRecord = makeTextRecordWithEntries(
+ List.of(new TextEntry("a", (byte[]) null)));
+
+ assertEquals(record1, record1);
+ assertEquals(record1, record2);
+ assertEquals(record1, record3);
+
+ assertNotEquals(nonEmptyRecord, record1);
+ assertNotEquals(nonEmptyRecord, record2);
+ assertNotEquals(nonEmptyRecord, record3);
+ }
+
private static String toHex(MdnsRecord record) throws IOException {
MdnsPacketWriter writer = new MdnsPacketWriter(MAX_PACKET_SIZE);
record.write(writer, record.getReceiptTime());
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
index b040ab6..0a8f108 100644
--- a/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsServiceCacheTest.kt
@@ -154,6 +154,11 @@
serviceCache.registerServiceExpiredCallback(cacheKey, callback)
}
+ private fun removeServices(
+ serviceCache: MdnsServiceCache,
+ cacheKey: CacheKey
+ ): Unit = runningOnHandlerAndReturn { serviceCache.removeServices(cacheKey) }
+
@Test
fun testAddAndRemoveService() {
val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
@@ -291,6 +296,37 @@
assertEquals(response4, responses[3])
}
+ @Test
+ fun testRemoveServices() {
+ val serviceCache = MdnsServiceCache(thread.looper, makeFlags(), clock)
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_1, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey1, createResponse(SERVICE_NAME_2, SERVICE_TYPE_1))
+ addOrUpdateService(serviceCache, cacheKey2, createResponse(SERVICE_NAME_1, SERVICE_TYPE_2))
+ val responses1 = getServices(serviceCache, cacheKey1)
+ assertEquals(2, responses1.size)
+ assertTrue(responses1.stream().anyMatch { response ->
+ response.serviceInstanceName == SERVICE_NAME_1
+ })
+ assertTrue(responses1.any { response ->
+ response.serviceInstanceName == SERVICE_NAME_2
+ })
+ val responses2 = getServices(serviceCache, cacheKey2)
+ assertEquals(1, responses2.size)
+ assertTrue(responses2.stream().anyMatch { response ->
+ response.serviceInstanceName == SERVICE_NAME_1
+ })
+
+ removeServices(serviceCache, cacheKey1)
+ val responses3 = getServices(serviceCache, cacheKey1)
+ assertEquals(0, responses3.size)
+ val responses4 = getServices(serviceCache, cacheKey2)
+ assertEquals(1, responses4.size)
+
+ removeServices(serviceCache, cacheKey2)
+ val responses5 = getServices(serviceCache, cacheKey2)
+ assertEquals(0, responses5.size)
+ }
+
private fun createResponse(
serviceInstanceName: String,
serviceType: String,
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
index 93f6e81..77b06b2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/CSIngressDiscardRuleTests.kt
@@ -26,7 +26,9 @@
import android.net.NetworkCapabilities.TRANSPORT_VPN
import android.net.NetworkCapabilities.TRANSPORT_WIFI
import android.net.NetworkRequest
+import android.net.VpnManager.TYPE_VPN_OEM
import android.net.VpnManager.TYPE_VPN_SERVICE
+import android.net.VpnManager.TYPE_VPN_LEGACY
import android.net.VpnTransportInfo
import android.os.Build
import androidx.test.filters.SmallTest
@@ -49,19 +51,19 @@
private const val TIMEOUT_MS = 1_000L
private const val LONG_TIMEOUT_MS = 5_000
-private fun vpnNc() = NetworkCapabilities.Builder()
- .addTransportType(TRANSPORT_VPN)
- .removeCapability(NET_CAPABILITY_NOT_VPN)
- .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
- .setTransportInfo(
- VpnTransportInfo(
- TYPE_VPN_SERVICE,
- "MySession12345",
- false /* bypassable */,
- false /* longLivedTcpConnectionsExpensive */
- )
- )
- .build()
+private fun vpnNc(vpnType: Int = TYPE_VPN_SERVICE) = NetworkCapabilities.Builder().apply {
+ addTransportType(TRANSPORT_VPN)
+ removeCapability(NET_CAPABILITY_NOT_VPN)
+ addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
+ setTransportInfo(
+ VpnTransportInfo(
+ vpnType,
+ "MySession12345",
+ false /* bypassable */,
+ false /* longLivedTcpConnectionsExpensive */
+ )
+ )
+}.build()
private fun wifiNc() = NetworkCapabilities.Builder()
.addTransportType(TRANSPORT_WIFI)
@@ -310,4 +312,38 @@
// IngressDiscardRule should not be added since feature is disabled
verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
}
+
+ fun doTestVpnIngressDiscardRule_VpnType(vpnType: Int, expectAddRule: Boolean) {
+ val nr = nr(TRANSPORT_VPN)
+ val cb = TestableNetworkCallback()
+ cm.registerNetworkCallback(nr, cb)
+ val nc = vpnNc(vpnType)
+ val lp = lp(VPN_IFNAME, IPV6_LINK_ADDRESS, LOCAL_IPV6_LINK_ADDRRESS)
+ val agent = Agent(nc = nc, lp = lp)
+ agent.connect()
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+
+ if (expectAddRule) {
+ verify(bpfNetMaps).setIngressDiscardRule(IPV6_ADDRESS, VPN_IFNAME)
+ } else {
+ verify(bpfNetMaps, never()).setIngressDiscardRule(any(), any())
+ }
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_ServiceVpn() {
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_SERVICE, expectAddRule = true)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_LegacyVpn() {
+ // IngressDiscardRule should not be added to Legacy VPN
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_LEGACY, expectAddRule = false)
+ }
+
+ @Test
+ fun testVpnIngressDiscardRule_OemVpn() {
+ // IngressDiscardRule should not be added to OEM VPN
+ doTestVpnIngressDiscardRule_VpnType(TYPE_VPN_OEM, expectAddRule = false)
+ }
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt b/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt
new file mode 100644
index 0000000..fc2a06c
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivityservice/CSQueuedCallbacksTest.kt
@@ -0,0 +1,636 @@
+/*
+ * 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.server.connectivityservice
+
+import android.app.ActivityManager.UidFrozenStateChangedCallback
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN
+import android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN
+import android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+import android.net.ConnectivityManager.BLOCKED_REASON_NONE
+import android.net.ConnectivitySettingsManager
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS
+import android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.LocalNetworkConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
+import android.net.NetworkCapabilities.TRANSPORT_WIFI
+import android.net.NetworkPolicyManager.NetworkPolicyCallback
+import android.net.NetworkRequest
+import android.os.Build
+import android.os.Process
+import com.android.server.CALLING_UID_UNMOCKED
+import com.android.server.CSAgentWrapper
+import com.android.server.CSTest
+import com.android.server.FromS
+import com.android.server.HANDLER_TIMEOUT_MS
+import com.android.server.connectivity.ConnectivityFlags.QUEUE_CALLBACKS_FOR_FROZEN_APPS
+import com.android.server.defaultLp
+import com.android.server.defaultNc
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRunner
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.LocalInfoChanged
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.RecorderCallback.CallbackEntry.Resumed
+import com.android.testutils.RecorderCallback.CallbackEntry.Suspended
+import com.android.testutils.TestableNetworkCallback
+import com.android.testutils.visibleOnHandlerThread
+import com.android.testutils.waitForIdleSerialExecutor
+import java.util.Collections
+import kotlin.test.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+
+private const val TEST_UID = 42
+
+@DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.TIRAMISU)
+@DevSdkIgnoreRunner.MonitorThreadLeak
+@RunWith(DevSdkIgnoreRunner::class)
+class CSQueuedCallbacksTest(freezingBehavior: FreezingBehavior) : CSTest() {
+ companion object {
+ enum class FreezingBehavior {
+ UID_FROZEN,
+ UID_NOT_FROZEN,
+ UID_FROZEN_FEATURE_DISABLED
+ }
+
+ // Use a parameterized test with / without freezing to make it easy to compare and make sure
+ // freezing behavior (which callbacks are sent in which order) stays close to what happens
+ // without freezing.
+ @JvmStatic
+ @Parameterized.Parameters(name = "freezingBehavior={0}")
+ fun freezingBehavior() = listOf(
+ FreezingBehavior.UID_FROZEN,
+ FreezingBehavior.UID_NOT_FROZEN,
+ FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ )
+
+ private val TAG = CSQueuedCallbacksTest::class.simpleName
+ ?: fail("Could not get test class name")
+ }
+
+ @get:Rule
+ val ignoreRule = DevSdkIgnoreRule()
+
+ private val mockedBlockedReasonsPerUid = Collections.synchronizedMap(mutableMapOf(
+ Process.myUid() to BLOCKED_REASON_NONE,
+ TEST_UID to BLOCKED_REASON_NONE
+ ))
+
+ private val freezeUids = freezingBehavior != FreezingBehavior.UID_NOT_FROZEN
+ private val expectAllCallbacks = freezingBehavior == FreezingBehavior.UID_NOT_FROZEN ||
+ freezingBehavior == FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ init {
+ setFeatureEnabled(
+ QUEUE_CALLBACKS_FOR_FROZEN_APPS,
+ freezingBehavior != FreezingBehavior.UID_FROZEN_FEATURE_DISABLED
+ )
+ }
+
+ @Before
+ fun subclassSetUp() {
+ // Ensure cellular stays up. CS is recreated for each test so no cleanup is necessary.
+// cm.requestNetwork(
+// NetworkRequest.Builder().addTransportType(TRANSPORT_CELLULAR).build(),
+// TestableNetworkCallback()
+// )
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_UpdatesAreReceived() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ val lpChangeOnConnect = agent.sendLpChange { setLinkAddresses("fe80:db8::123/64") }
+ val ncChangeOnConnect = agent.sendNcChange {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ val lpChange1WhileFrozen = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::126/64")
+ }
+ val ncChange1WhileFrozen = agent.sendNcChange {
+ removeCapability(NET_CAPABILITY_NOT_ROAMING)
+ }
+ val ncChange2WhileFrozen = agent.sendNcChange {
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ }
+ val lpChange2WhileFrozen = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::125/64")
+ }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ // Verify callbacks that are sent before freezing
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ cb.expectLpWith(agent, lpChangeOnConnect)
+ cb.expectNcWith(agent, ncChangeOnConnect)
+
+ // Below callbacks should be skipped if the processes were frozen, since a single callback
+ // will be sent with the latest state after unfreezing
+ if (expectAllCallbacks) {
+ cb.expectLpWith(agent, lpChange1WhileFrozen)
+ cb.expectNcWith(agent, ncChange1WhileFrozen)
+ }
+
+ cb.expectNcWith(agent, ncChange2WhileFrozen)
+ cb.expectLpWith(agent, lpChange2WhileFrozen)
+
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_SuspendedUnsuspendedWhileFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val rmCap = agent.sendNcChange { removeCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ val addCap = agent.sendNcChange { addCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectNcWith(agent, rmCap)
+ cb.expect<Suspended>(agent)
+ cb.expectNcWith(agent, addCap)
+ cb.expect<Resumed>(agent)
+ } else {
+ // When frozen, a single NetworkCapabilitiesChange will be sent at unfreezing time,
+ // with nc actually identical to the original ones. This is because NetworkCapabilities
+ // callbacks were sent, but CS does not keep initial NetworkCapabilities in memory, so
+ // it cannot detect A->B->A.
+ cb.expect<CapabilitiesChanged>(agent) {
+ it.caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_UnsuspendedWhileFrozen_GetResumedCallbackWhenUnfrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ val rmCap = agent.sendNcChange { removeCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val addCap = agent.sendNcChange { addCapability(NET_CAPABILITY_NOT_SUSPENDED) }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ cb.expectNcWith(agent, rmCap)
+ cb.expect<Suspended>(agent)
+ cb.expectNcWith(agent, addCap)
+ cb.expect<Resumed>(agent)
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_BlockedUnblockedWhileFrozen_SingleCallbackIfFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ setUidsBlockedForDataSaver(false, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ }
+ // The unblocked callback is sent in any case (with the latest blocked reason), as the
+ // blocked reason may have changed, and ConnectivityService cannot know that it is the same
+ // as the original reason as it does not keep pre-freeze blocked reasons in memory.
+ cb.expect<BlockedStatus>(agent) { !it.blocked }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testFrozenWhileNetworkConnects_BlockedWhileFrozen_GetLastBlockedCallbackOnlyIfFrozen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+
+ maybeSetUidsFrozen(true, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ setUidsBlockedForDataSaver(false, TEST_UID)
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(agent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ cb.expect<BlockedStatus>(agent) { !it.blocked }
+ }
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkCallback_NetworkToggledWhileFrozen_NotSeen() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ wifiAgent.disconnect()
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ cb.expect<Lost>(wifiAgent)
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkCallback_NetworkAppearedWhileFrozen_ReceiveLatestInfoInOnAvailable() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(NetworkRequest.Builder().build(), cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val agent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ waitForIdle()
+ agent.makeValidationSuccess()
+ val lpChange = agent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ val suspendedChange = agent.sendNcChange {
+ removeCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ }
+ setUidsBlockedForDataSaver(true, TEST_UID)
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ val expectLatestStatusInOnAvailable = !expectAllCallbacks
+ cb.expectAvailableCallbacks(
+ agent.network,
+ suspended = expectLatestStatusInOnAvailable,
+ validated = expectLatestStatusInOnAvailable,
+ blocked = expectLatestStatusInOnAvailable
+ )
+ if (expectAllCallbacks) {
+ cb.expectNcWith(agent) { addCapability(NET_CAPABILITY_VALIDATED) }
+ cb.expectLpWith(agent, lpChange)
+ cb.expectNcWith(agent, suspendedChange)
+ cb.expect<Suspended>(agent)
+ cb.expect<BlockedStatus>(agent) { it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ fun testNetworkCallback_LocalNetworkAppearedWhileFrozen_ReceiveLatestInfoInOnAvailable() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerNetworkCallback(
+ NetworkRequest.Builder().addCapability(NET_CAPABILITY_LOCAL_NETWORK).build(),
+ cb
+ )
+ }
+ val upstreamAgent = Agent(
+ nc = defaultNc()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(NET_CAPABILITY_INTERNET),
+ lp = defaultLp().apply { interfaceName = "wlan0" }
+ ).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ val lnc = LocalNetworkConfig.Builder().build()
+ val localAgent = Agent(
+ nc = defaultNc()
+ .addCapability(NET_CAPABILITY_LOCAL_NETWORK)
+ .removeCapability(NET_CAPABILITY_INTERNET),
+ lp = defaultLp().apply { interfaceName = "local42" },
+ lnc = FromS(lnc)
+ ).apply { connect() }
+ localAgent.sendLocalNetworkConfig(
+ LocalNetworkConfig.Builder()
+ .setUpstreamSelector(
+ NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+ )
+ .build()
+ )
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(
+ localAgent.network,
+ validated = false,
+ upstream = if (expectAllCallbacks) null else upstreamAgent.network
+ )
+ if (expectAllCallbacks) {
+ cb.expect<LocalInfoChanged>(localAgent) {
+ it.info.upstreamNetwork == upstreamAgent.network
+ }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkRequest_NetworkSwitchesWhileFrozen_ReceiveLastNetworkUpdatesOnly() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.requestNetwork(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ val ethAgent = Agent(TRANSPORT_ETHERNET).apply { connect() }
+ waitForIdle()
+ ethAgent.makeValidationSuccess()
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ cb.expectAvailableCallbacks(ethAgent.network, validated = false)
+ cb.expectNcWith(ethAgent) { addCapability(NET_CAPABILITY_VALIDATED) }
+ } else {
+ cb.expectAvailableCallbacks(ethAgent.network, validated = true)
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testNetworkRequest_NetworkSwitchesBackWhileFrozen_ReceiveNoAvailableCallback() {
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.requestNetwork(NetworkRequest.Builder().build(), cb)
+ }
+ val cellAgent = Agent(TRANSPORT_CELLULAR).apply { connect() }
+ maybeSetUidsFrozen(true, TEST_UID)
+ val wifiAgent = Agent(TRANSPORT_WIFI).apply { connect() }
+ waitForIdle()
+
+ // CS switches back to validated cell over non-validated Wi-Fi
+ cellAgent.makeValidationSuccess()
+ val cellLpChange = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ setUidsBlockedForDataSaver(true, TEST_UID)
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectAvailableCallbacks(wifiAgent.network, validated = false)
+ // There is an extra "double validated" CapabilitiesChange callback (b/245893397), so
+ // callbacks are (AVAIL, NC, LP), extra NC, then further updates (LP and BLK here).
+ cb.expectAvailableDoubleValidatedCallbacks(cellAgent.network)
+ cb.expectLpWith(cellAgent, cellLpChange)
+ cb.expect<BlockedStatus>(cellAgent) { it.blocked }
+ } else {
+ cb.expectNcWith(cellAgent) {
+ addCapability(NET_CAPABILITY_VALIDATED)
+ }
+ cb.expectLpWith(cellAgent, cellLpChange)
+ cb.expect<BlockedStatus>(cellAgent) { it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testTrackDefaultRequest_AppFrozenWhilePerAppDefaultRequestFiled_ReceiveChangeCallbacks() {
+ val cellAgent = Agent(TRANSPORT_CELLULAR, baseNc = makeInternetNc()).apply { connect() }
+ waitForIdle()
+
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerDefaultNetworkCallback(cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ // Change LinkProperties twice before the per-app network request is applied
+ val lpChange1 = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ val lpChange2 = cellAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::124/64")
+ }
+ setMobileDataPreferredUids(setOf(TEST_UID))
+
+ // Change NetworkCapabilities after the per-app network request is applied
+ val ncChange = cellAgent.sendNcChange {
+ addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ }
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ // Even if a per-app network request was filed to replace the default network request for
+ // the app, all network change callbacks are received
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ if (expectAllCallbacks) {
+ cb.expectLpWith(cellAgent, lpChange1)
+ }
+ cb.expectLpWith(cellAgent, lpChange2)
+ cb.expectNcWith(cellAgent, ncChange)
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ @Test
+ fun testTrackDefaultRequest_AppFrozenWhilePerAppDefaultToggled_GetStatusUpdateCallbacksOnly() {
+ // Add validated Wi-Fi and non-validated cell, expect Wi-Fi is preferred by default
+ val wifiAgent = Agent(TRANSPORT_WIFI, baseNc = makeInternetNc()).apply { connect() }
+ wifiAgent.makeValidationSuccess()
+ val cellAgent = Agent(TRANSPORT_CELLULAR, baseNc = makeInternetNc()).apply { connect() }
+ waitForIdle()
+
+ val cb = TestableNetworkCallback(logTag = TAG)
+ withCallingUid(TEST_UID) {
+ cm.registerDefaultNetworkCallback(cb)
+ }
+ maybeSetUidsFrozen(true, TEST_UID)
+
+ // LP change on the original Wi-Fi network
+ val lpChange = wifiAgent.sendLpChange {
+ setLinkAddresses("fe80:db8::123/64")
+ }
+ // Set per-app default to cell, then unset it
+ setMobileDataPreferredUids(setOf(TEST_UID))
+ setMobileDataPreferredUids(emptySet())
+
+ maybeSetUidsFrozen(false, TEST_UID)
+
+ cb.expectAvailableCallbacks(wifiAgent.network)
+ if (expectAllCallbacks) {
+ cb.expectLpWith(wifiAgent, lpChange)
+ cb.expectAvailableCallbacks(cellAgent.network, validated = false)
+ // Cellular stops being foreground since it is now matched for this app
+ cb.expect<CapabilitiesChanged> { it.caps.hasCapability(NET_CAPABILITY_FOREGROUND) }
+ cb.expectAvailableCallbacks(wifiAgent.network)
+ } else {
+ // After switching to cell and back while frozen, only network attribute update
+ // callbacks (and not AVAILABLE) for the original Wi-Fi network should be sent
+ cb.expect<CapabilitiesChanged>(wifiAgent)
+ cb.expectLpWith(wifiAgent, lpChange)
+ cb.expect<BlockedStatus> { !it.blocked }
+ }
+ cb.assertNoCallback(timeoutMs = 0L)
+ }
+
+ private fun setUidsBlockedForDataSaver(blocked: Boolean, vararg uid: Int) {
+ val reason = if (blocked) {
+ BLOCKED_METERED_REASON_DATA_SAVER
+ } else {
+ BLOCKED_REASON_NONE
+ }
+ if (deps.isAtLeastV) {
+ visibleOnHandlerThread(csHandler) {
+ service.handleBlockedReasonsChanged(uid.map { android.util.Pair(it, reason) })
+ }
+ } else {
+ notifyLegacyBlockedReasonChanged(reason, uid)
+ waitForIdle()
+ }
+ }
+
+ @Suppress("DEPRECATION")
+ private fun notifyLegacyBlockedReasonChanged(reason: Int, uids: IntArray) {
+ val cbCaptor = ArgumentCaptor.forClass(NetworkPolicyCallback::class.java)
+ verify(context.networkPolicyManager).registerNetworkPolicyCallback(
+ any(),
+ cbCaptor.capture()
+ )
+ uids.forEach {
+ cbCaptor.value.onUidBlockedReasonChanged(it, reason)
+ }
+ }
+
+ private fun withCallingUid(uid: Int, action: () -> Unit) {
+ deps.callingUid = uid
+ action()
+ deps.callingUid = CALLING_UID_UNMOCKED
+ }
+
+ private fun getUidFrozenStateChangedCallback(): UidFrozenStateChangedCallback {
+ val captor = ArgumentCaptor.forClass(UidFrozenStateChangedCallback::class.java)
+ verify(activityManager).registerUidFrozenStateChangedCallback(any(), captor.capture())
+ return captor.value
+ }
+
+ private fun maybeSetUidsFrozen(frozen: Boolean, vararg uids: Int) {
+ if (!freezeUids) return
+ val state = if (frozen) UID_FROZEN_STATE_FROZEN else UID_FROZEN_STATE_UNFROZEN
+ getUidFrozenStateChangedCallback()
+ .onUidFrozenStateChanged(uids, IntArray(uids.size) { state })
+ waitForIdle()
+ }
+
+ private fun CSAgentWrapper.makeValidationSuccess() {
+ setValidationResult(
+ NETWORK_VALIDATION_RESULT_VALID,
+ probesCompleted = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTPS,
+ probesSucceeded = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTPS
+ )
+ cm.reportNetworkConnectivity(network, true)
+ // Ensure validation is scheduled
+ waitForIdle()
+ // Ensure validation completes on mock executor
+ waitForIdleSerialExecutor(CSTestExecutor, HANDLER_TIMEOUT_MS)
+ // Ensure validation results are processed
+ waitForIdle()
+ }
+
+ private fun setMobileDataPreferredUids(uids: Set<Int>) {
+ ConnectivitySettingsManager.setMobileDataPreferredUids(context, uids)
+ service.updateMobileDataPreferredUids()
+ waitForIdle()
+ }
+}
+
+private fun makeInternetNc() = NetworkCapabilities.Builder(defaultNc())
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .build()
+
+private fun CSAgentWrapper.sendLpChange(
+ mutator: LinkProperties.() -> Unit
+): LinkProperties.() -> Unit {
+ lp.mutator()
+ sendLinkProperties(lp)
+ return mutator
+}
+
+private fun CSAgentWrapper.sendNcChange(
+ mutator: NetworkCapabilities.() -> Unit
+): NetworkCapabilities.() -> Unit {
+ nc.mutator()
+ sendNetworkCapabilities(nc)
+ return mutator
+}
+
+private fun TestableNetworkCallback.expectLpWith(
+ agent: CSAgentWrapper,
+ change: LinkProperties.() -> Unit
+) = expect<LinkPropertiesChanged>(agent) {
+ // This test uses changes that are no-op when already applied (idempotent): verify that the
+ // change is already applied.
+ it.lp == LinkProperties(it.lp).apply(change)
+}
+
+private fun TestableNetworkCallback.expectNcWith(
+ agent: CSAgentWrapper,
+ change: NetworkCapabilities.() -> Unit
+) = expect<CapabilitiesChanged>(agent) {
+ it.caps == NetworkCapabilities(it.caps).apply(change)
+}
+
+private fun LinkProperties.setLinkAddresses(vararg addrs: String) {
+ setLinkAddresses(addrs.map { LinkAddress(it) })
+}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
index 13c5cbc..1f5ee32 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSAgentWrapper.kt
@@ -192,7 +192,8 @@
connect()
}
- fun setProbesStatus(probesCompleted: Int, probesSucceeded: Int) {
+ fun setValidationResult(result: Int, probesCompleted: Int, probesSucceeded: Int) {
+ nmValidationResult = result
nmProbesCompleted = probesCompleted
nmProbesSucceeded = probesSucceeded
}
@@ -204,8 +205,10 @@
// in the beginning. Because NETWORK_VALIDATION_PROBE_HTTP is the decisive probe for captive
// portal, considering the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet and set only
// DNS and HTTP probes completed.
- setProbesStatus(
- NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP /* probesCompleted */,
- VALIDATION_RESULT_INVALID /* probesSucceeded */)
+ setValidationResult(
+ VALIDATION_RESULT_INVALID,
+ probesCompleted = NETWORK_VALIDATION_PROBE_DNS or NETWORK_VALIDATION_PROBE_HTTP,
+ probesSucceeded = NO_PROBE_RESULT
+ )
}
}
diff --git a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
index de56ae5..46c25d2 100644
--- a/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
+++ b/tests/unit/java/com/android/server/connectivityservice/base/CSTest.kt
@@ -538,8 +538,12 @@
provider: NetworkProvider? = null
) = CSAgentWrapper(context, deps, csHandlerThread, networkStack,
nac, nc, lp, lnc, score, provider)
- fun Agent(vararg transports: Int, lp: LinkProperties = defaultLp()): CSAgentWrapper {
- val nc = NetworkCapabilities.Builder().apply {
+ fun Agent(
+ vararg transports: Int,
+ baseNc: NetworkCapabilities = defaultNc(),
+ lp: LinkProperties = defaultLp()
+ ): CSAgentWrapper {
+ val nc = NetworkCapabilities.Builder(baseNc).apply {
transports.forEach {
addTransportType(it)
}
diff --git a/thread/framework/java/android/net/thread/ThreadConfiguration.java b/thread/framework/java/android/net/thread/ThreadConfiguration.java
index e09b3a6..be2632c 100644
--- a/thread/framework/java/android/net/thread/ThreadConfiguration.java
+++ b/thread/framework/java/android/net/thread/ThreadConfiguration.java
@@ -15,7 +15,9 @@
*/
package android.net.thread;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,19 +39,19 @@
* @see ThreadNetworkController#unregisterConfigurationCallback
* @hide
*/
-// @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
-// @SystemApi
+@FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+@SystemApi
public final class ThreadConfiguration implements Parcelable {
private final boolean mNat64Enabled;
- private final boolean mDhcp6PdEnabled;
+ private final boolean mDhcpv6PdEnabled;
private ThreadConfiguration(Builder builder) {
- this(builder.mNat64Enabled, builder.mDhcp6PdEnabled);
+ this(builder.mNat64Enabled, builder.mDhcpv6PdEnabled);
}
- private ThreadConfiguration(boolean nat64Enabled, boolean dhcp6PdEnabled) {
+ private ThreadConfiguration(boolean nat64Enabled, boolean dhcpv6PdEnabled) {
this.mNat64Enabled = nat64Enabled;
- this.mDhcp6PdEnabled = dhcp6PdEnabled;
+ this.mDhcpv6PdEnabled = dhcpv6PdEnabled;
}
/** Returns {@code true} if NAT64 is enabled. */
@@ -58,8 +60,8 @@
}
/** Returns {@code true} if DHCPv6 Prefix Delegation is enabled. */
- public boolean isDhcp6PdEnabled() {
- return mDhcp6PdEnabled;
+ public boolean isDhcpv6PdEnabled() {
+ return mDhcpv6PdEnabled;
}
@Override
@@ -71,13 +73,13 @@
} else {
ThreadConfiguration otherConfig = (ThreadConfiguration) other;
return mNat64Enabled == otherConfig.mNat64Enabled
- && mDhcp6PdEnabled == otherConfig.mDhcp6PdEnabled;
+ && mDhcpv6PdEnabled == otherConfig.mDhcpv6PdEnabled;
}
}
@Override
public int hashCode() {
- return Objects.hash(mNat64Enabled, mDhcp6PdEnabled);
+ return Objects.hash(mNat64Enabled, mDhcpv6PdEnabled);
}
@Override
@@ -85,7 +87,7 @@
StringBuilder sb = new StringBuilder();
sb.append('{');
sb.append("Nat64Enabled=").append(mNat64Enabled);
- sb.append(", Dhcp6PdEnabled=").append(mDhcp6PdEnabled);
+ sb.append(", Dhcpv6PdEnabled=").append(mDhcpv6PdEnabled);
sb.append('}');
return sb.toString();
}
@@ -98,7 +100,7 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mNat64Enabled);
- dest.writeBoolean(mDhcp6PdEnabled);
+ dest.writeBoolean(mDhcpv6PdEnabled);
}
public static final @NonNull Creator<ThreadConfiguration> CREATOR =
@@ -107,7 +109,7 @@
public ThreadConfiguration createFromParcel(Parcel in) {
ThreadConfiguration.Builder builder = new ThreadConfiguration.Builder();
builder.setNat64Enabled(in.readBoolean());
- builder.setDhcp6PdEnabled(in.readBoolean());
+ builder.setDhcpv6PdEnabled(in.readBoolean());
return builder.build();
}
@@ -117,10 +119,14 @@
}
};
- /** The builder for creating {@link ThreadConfiguration} objects. */
+ /**
+ * The builder for creating {@link ThreadConfiguration} objects.
+ *
+ * @hide
+ */
public static final class Builder {
private boolean mNat64Enabled = false;
- private boolean mDhcp6PdEnabled = false;
+ private boolean mDhcpv6PdEnabled = false;
/** Creates a new {@link Builder} object with all features disabled. */
public Builder() {}
@@ -134,7 +140,7 @@
Objects.requireNonNull(config);
mNat64Enabled = config.mNat64Enabled;
- mDhcp6PdEnabled = config.mDhcp6PdEnabled;
+ mDhcpv6PdEnabled = config.mDhcpv6PdEnabled;
}
/**
@@ -156,8 +162,8 @@
* IPv6.
*/
@NonNull
- public Builder setDhcp6PdEnabled(boolean enabled) {
- this.mDhcp6PdEnabled = enabled;
+ public Builder setDhcpv6PdEnabled(boolean enabled) {
+ this.mDhcpv6PdEnabled = enabled;
return this;
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 30b3d6a..b4e581c 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -619,16 +619,15 @@
/**
* Registers a callback to be called when the configuration is changed.
*
- * <p>Upon return of this method, {@code callback} will be invoked immediately with the new
+ * <p>Upon return of this method, {@code callback} will be invoked immediately with the current
* {@link ThreadConfiguration}.
*
* @param executor the executor to execute the {@code callback}
* @param callback the callback to receive Thread configuration changes
* @throws IllegalArgumentException if {@code callback} has already been registered
- * @hide
*/
- // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
- // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
public void registerConfigurationCallback(
@NonNull @CallbackExecutor Executor executor,
@NonNull Consumer<ThreadConfiguration> callback) {
@@ -656,10 +655,9 @@
* @param callback the callback which has been registered with {@link
* #registerConfigurationCallback}
* @throws IllegalArgumentException if {@code callback} hasn't been registered
- * @hide
*/
- // @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
- // @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
+ @FlaggedApi(ThreadNetworkFlags.FLAG_CONFIGURATION_ENABLED)
+ @RequiresPermission(permission.THREAD_NETWORK_PRIVILEGED)
public void unregisterConfigurationCallback(@NonNull Consumer<ThreadConfiguration> callback) {
requireNonNull(callback, "callback cannot be null");
synchronized (mConfigurationCallbackMapLock) {
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index e6f272b..b621a6a 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -108,6 +108,7 @@
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
+import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
@@ -119,7 +120,7 @@
import com.android.server.ServiceManagerWrapper;
import com.android.server.connectivity.ConnectivityResources;
import com.android.server.thread.openthread.BackboneRouterState;
-import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
+import com.android.server.thread.openthread.BorderRouterConfiguration;
import com.android.server.thread.openthread.DnsTxtAttribute;
import com.android.server.thread.openthread.IChannelMasksReceiver;
import com.android.server.thread.openthread.IOtDaemon;
@@ -212,7 +213,7 @@
private boolean mUserRestricted;
private boolean mForceStopOtDaemonEnabled;
- private BorderRouterConfigurationParcel mBorderRouterConfig;
+ private BorderRouterConfiguration mBorderRouterConfig;
@VisibleForTesting
ThreadNetworkControllerService(
@@ -237,7 +238,11 @@
mInfraIfController = infraIfController;
mUpstreamNetworkRequest = newUpstreamNetworkRequest();
mNetworkToInterface = new HashMap<Network, String>();
- mBorderRouterConfig = new BorderRouterConfigurationParcel();
+ mBorderRouterConfig =
+ new BorderRouterConfiguration.Builder()
+ .setIsBorderRoutingEnabled(true)
+ .setInfraInterfaceName(null)
+ .build();
mPersistentSettings = persistentSettings;
mNsdPublisher = nsdPublisher;
mUserManager = userManager;
@@ -271,10 +276,12 @@
}
private NetworkRequest newUpstreamNetworkRequest() {
- NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities();
+ NetworkRequest.Builder builder = new NetworkRequest.Builder();
if (mUpstreamTestNetworkSpecifier != null) {
- return builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ // Test networks don't have NET_CAPABILITY_TRUSTED
+ return builder.removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
.setNetworkSpecifier(mUpstreamTestNetworkSpecifier)
.build();
}
@@ -1225,38 +1232,54 @@
}
}
- private void enableBorderRouting(String infraIfName) {
- if (mBorderRouterConfig.isBorderRoutingEnabled
- && infraIfName.equals(mBorderRouterConfig.infraInterfaceName)) {
+ private void configureBorderRouter(BorderRouterConfiguration borderRouterConfig) {
+ if (mBorderRouterConfig.equals(borderRouterConfig)) {
return;
}
- Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
+ Log.i(
+ TAG,
+ "Configuring Border Router: " + mBorderRouterConfig + " -> " + borderRouterConfig);
+ mBorderRouterConfig = borderRouterConfig;
+ ParcelFileDescriptor infraIcmp6Socket = null;
+ if (mBorderRouterConfig.infraInterfaceName != null) {
+ try {
+ infraIcmp6Socket =
+ mInfraIfController.createIcmp6Socket(
+ mBorderRouterConfig.infraInterfaceName);
+ } catch (IOException e) {
+ Log.i(TAG, "Failed to create ICMPv6 socket on infra network interface", e);
+ }
+ }
try {
- mBorderRouterConfig.infraInterfaceName = infraIfName;
- mBorderRouterConfig.infraInterfaceIcmp6Socket =
- mInfraIfController.createIcmp6Socket(infraIfName);
- mBorderRouterConfig.isBorderRoutingEnabled = true;
-
getOtDaemon()
.configureBorderRouter(
- mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
- } catch (RemoteException | IOException | ThreadNetworkException e) {
- Log.w(TAG, "Failed to enable border routing", e);
+ mBorderRouterConfig,
+ infraIcmp6Socket,
+ new ConfigureBorderRouterStatusReceiver());
+ } catch (RemoteException | ThreadNetworkException e) {
+ Log.w(TAG, "Failed to configure border router " + mBorderRouterConfig, e);
}
}
+ private void enableBorderRouting(String infraIfName) {
+ BorderRouterConfiguration borderRouterConfig =
+ newBorderRouterConfigBuilder(mBorderRouterConfig)
+ .setIsBorderRoutingEnabled(true)
+ .setInfraInterfaceName(infraIfName)
+ .build();
+ Log.i(TAG, "Enable border routing on AIL: " + infraIfName);
+ configureBorderRouter(borderRouterConfig);
+ }
+
private void disableBorderRouting() {
mUpstreamNetwork = null;
- mBorderRouterConfig.infraInterfaceName = null;
- mBorderRouterConfig.infraInterfaceIcmp6Socket = null;
- mBorderRouterConfig.isBorderRoutingEnabled = false;
- try {
- getOtDaemon()
- .configureBorderRouter(
- mBorderRouterConfig, new ConfigureBorderRouterStatusReceiver());
- } catch (RemoteException | ThreadNetworkException e) {
- Log.w(TAG, "Failed to disable border routing", e);
- }
+ BorderRouterConfiguration borderRouterConfig =
+ newBorderRouterConfigBuilder(mBorderRouterConfig)
+ .setIsBorderRoutingEnabled(false)
+ .setInfraInterfaceName(null)
+ .build();
+ Log.i(TAG, "Disabling border routing");
+ configureBorderRouter(borderRouterConfig);
}
private void handleThreadInterfaceStateChanged(boolean isUp) {
@@ -1357,6 +1380,13 @@
return builder.build();
}
+ private static BorderRouterConfiguration.Builder newBorderRouterConfigBuilder(
+ BorderRouterConfiguration brConfig) {
+ return new BorderRouterConfiguration.Builder()
+ .setIsBorderRoutingEnabled(brConfig.isBorderRoutingEnabled)
+ .setInfraInterfaceName(brConfig.infraInterfaceName);
+ }
+
private static final class CallbackMetadata {
private static long gId = 0;
diff --git a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
index 747cc96..7c4c72d 100644
--- a/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
+++ b/thread/service/java/com/android/server/thread/ThreadPersistentSettings.java
@@ -197,7 +197,7 @@
return false;
}
putObject(CONFIG_NAT64_ENABLED.key, configuration.isNat64Enabled());
- putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcp6PdEnabled());
+ putObject(CONFIG_DHCP6_PD_ENABLED.key, configuration.isDhcpv6PdEnabled());
writeToStoreFile();
return true;
}
@@ -206,7 +206,7 @@
public ThreadConfiguration getConfiguration() {
return new ThreadConfiguration.Builder()
.setNat64Enabled(get(CONFIG_NAT64_ENABLED))
- .setDhcp6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
+ .setDhcpv6PdEnabled(get(CONFIG_DHCP6_PD_ENABLED))
.build();
}
diff --git a/thread/tests/cts/Android.bp b/thread/tests/cts/Android.bp
index c1cf0a0..6db7c9c 100644
--- a/thread/tests/cts/Android.bp
+++ b/thread/tests/cts/Android.bp
@@ -21,9 +21,11 @@
android_test {
name: "CtsThreadNetworkTestCases",
- defaults: ["cts_defaults"],
+ defaults: [
+ "cts_defaults",
+ "framework-connectivity-test-defaults",
+ ],
min_sdk_version: "33",
- sdk_version: "test_current",
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
srcs: [
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java
new file mode 100644
index 0000000..386412e
--- /dev/null
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadConfigurationTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.net.thread.cts;
+
+import static com.android.testutils.ParcelUtils.assertParcelingIsLossless;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.thread.ThreadConfiguration;
+import android.net.thread.utils.ThreadFeatureCheckerRule;
+import android.net.thread.utils.ThreadFeatureCheckerRule.RequiresThreadFeature;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/** Tests for {@link ThreadConfiguration}. */
+@SmallTest
+@RequiresThreadFeature
+@RunWith(Parameterized.class)
+public final class ThreadConfigurationTest {
+ @Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
+
+ public final boolean mIsNat64Enabled;
+ public final boolean mIsDhcpv6PdEnabled;
+
+ @Parameterized.Parameters
+ public static Collection configArguments() {
+ return Arrays.asList(
+ new Object[][] {
+ {false, false}, // All disabled
+ {true, false}, // NAT64 enabled
+ {false, true}, // DHCP6-PD enabled
+ {true, true}, // All enabled
+ });
+ }
+
+ public ThreadConfigurationTest(boolean isNat64Enabled, boolean isDhcpv6PdEnabled) {
+ mIsNat64Enabled = isNat64Enabled;
+ mIsDhcpv6PdEnabled = isDhcpv6PdEnabled;
+ }
+
+ @Test
+ public void parcelable_parcelingIsLossLess() {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+ assertParcelingIsLossless(config);
+ }
+
+ @Test
+ public void builder_correctValuesAreSet() {
+ ThreadConfiguration config =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+
+ assertThat(config.isNat64Enabled()).isEqualTo(mIsNat64Enabled);
+ assertThat(config.isDhcpv6PdEnabled()).isEqualTo(mIsDhcpv6PdEnabled);
+ }
+
+ @Test
+ public void builderConstructor_configsAreEqual() {
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(mIsNat64Enabled)
+ .setDhcpv6PdEnabled(mIsDhcpv6PdEnabled)
+ .build();
+ ThreadConfiguration config2 = new ThreadConfiguration.Builder(config1).build();
+ assertThat(config1).isEqualTo(config2);
+ }
+}
diff --git a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
index 22e7a98..1a101b6 100644
--- a/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
+++ b/thread/tests/cts/src/android/net/thread/cts/ThreadNetworkControllerTest.java
@@ -45,6 +45,7 @@
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import android.content.Context;
@@ -57,6 +58,7 @@
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.OperationalDatasetTimestamp;
import android.net.thread.PendingOperationalDataset;
+import android.net.thread.ThreadConfiguration;
import android.net.thread.ThreadNetworkController;
import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
import android.net.thread.ThreadNetworkController.StateCallback;
@@ -94,9 +96,11 @@
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/** CTS tests for {@link ThreadNetworkController}. */
@@ -109,11 +113,14 @@
private static final int NETWORK_CALLBACK_TIMEOUT_MILLIS = 10 * 1000;
private static final int CALLBACK_TIMEOUT_MILLIS = 1_000;
private static final int ENABLED_TIMEOUT_MILLIS = 2_000;
+ private static final int SET_CONFIGURATION_TIMEOUT_MILLIS = 1_000;
private static final int SERVICE_DISCOVERY_TIMEOUT_MILLIS = 30_000;
private static final int SERVICE_LOST_TIMEOUT_MILLIS = 20_000;
private static final String MESHCOP_SERVICE_TYPE = "_meshcop._udp";
private static final String THREAD_NETWORK_PRIVILEGED =
"android.permission.THREAD_NETWORK_PRIVILEGED";
+ private static final ThreadConfiguration DEFAULT_CONFIG =
+ new ThreadConfiguration.Builder().build();
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -126,6 +133,9 @@
private HandlerThread mHandlerThread;
private TapTestNetworkTracker mTestNetworkTracker;
+ private final List<Consumer<ThreadConfiguration>> mConfigurationCallbacksToCleanUp =
+ new ArrayList<>();
+
@Before
public void setUp() throws Exception {
mController =
@@ -140,6 +150,7 @@
mHandlerThread.start();
setEnabledAndWait(mController, true);
+ setConfigurationAndWait(mController, DEFAULT_CONFIG);
}
@After
@@ -147,6 +158,18 @@
dropAllPermissions();
leaveAndWait(mController);
tearDownTestNetwork();
+ setConfigurationAndWait(mController, DEFAULT_CONFIG);
+ for (Consumer<ThreadConfiguration> configurationCallback :
+ mConfigurationCallbacksToCleanUp) {
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterConfigurationCallback(configurationCallback));
+ } catch (IllegalArgumentException e) {
+ // Ignore the exception when the callback is not registered.
+ }
+ }
+ mConfigurationCallbacksToCleanUp.clear();
}
@Test
@@ -831,6 +854,152 @@
NET_CAPABILITY_TRUSTED);
}
+ @Test
+ public void setConfiguration_null_throwsNullPointerException() throws Exception {
+ CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
+ assertThrows(
+ NullPointerException.class,
+ () ->
+ mController.setConfiguration(
+ null, mExecutor, newOutcomeReceiver(setConfigFuture)));
+ }
+
+ @Test
+ public void setConfiguration_noPermissions_throwsSecurityException() throws Exception {
+ ThreadConfiguration configuration =
+ new ThreadConfiguration.Builder().setNat64Enabled(true).build();
+ CompletableFuture<Void> setConfigFuture = new CompletableFuture<>();
+ assertThrows(
+ SecurityException.class,
+ () -> {
+ mController.setConfiguration(
+ configuration, mExecutor, newOutcomeReceiver(setConfigFuture));
+ });
+ }
+
+ @Test
+ public void registerConfigurationCallback_permissionsGranted_returnsCurrentStatus()
+ throws Exception {
+ CompletableFuture<ThreadConfiguration> getConfigFuture = new CompletableFuture<>();
+ Consumer<ThreadConfiguration> callback = getConfigFuture::complete;
+
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+ assertThat(getConfigFuture.get(CALLBACK_TIMEOUT_MILLIS, MILLISECONDS))
+ .isEqualTo(DEFAULT_CONFIG);
+ }
+
+ @Test
+ public void registerConfigurationCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ dropAllPermissions();
+
+ assertThrows(
+ SecurityException.class,
+ () -> registerConfigurationCallback(mController, mExecutor, config -> {}));
+ }
+
+ @Test
+ public void registerConfigurationCallback_returnsUpdatedConfigurations() throws Exception {
+ CompletableFuture<Void> setFuture1 = new CompletableFuture<>();
+ CompletableFuture<Void> setFuture2 = new CompletableFuture<>();
+ ConfigurationListener listener = new ConfigurationListener(mController);
+ ThreadConfiguration config1 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(true)
+ .setDhcpv6PdEnabled(true)
+ .build();
+ ThreadConfiguration config2 =
+ new ThreadConfiguration.Builder()
+ .setNat64Enabled(false)
+ .setDhcpv6PdEnabled(true)
+ .build();
+
+ try {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setConfiguration(
+ config1, mExecutor, newOutcomeReceiver(setFuture1)));
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ mController.setConfiguration(
+ config2, mExecutor, newOutcomeReceiver(setFuture2)));
+ setFuture1.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+ setFuture2.get(ENABLED_TIMEOUT_MILLIS, MILLISECONDS);
+
+ listener.expectConfiguration(DEFAULT_CONFIG);
+ listener.expectConfiguration(config1);
+ listener.expectConfiguration(config2);
+ listener.expectNoMoreConfiguration();
+ } finally {
+ listener.unregisterConfigurationCallback();
+ }
+ }
+
+ @Test
+ public void registerConfigurationCallback_alreadyRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+
+ Consumer<ThreadConfiguration> callback = config -> {};
+ registerConfigurationCallback(mController, mExecutor, callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_noPermissions_throwsSecurityException()
+ throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> registerConfigurationCallback(mController, mExecutor, callback));
+
+ assertThrows(
+ SecurityException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_callbackRegistered_success() throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> {
+ registerConfigurationCallback(mController, mExecutor, callback);
+ mController.unregisterConfigurationCallback(callback);
+ });
+ }
+
+ @Test
+ public void
+ unregisterConfigurationCallback_callbackNotRegistered_throwsIllegalArgumentException()
+ throws Exception {
+ Consumer<ThreadConfiguration> callback = config -> {};
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
+ @Test
+ public void unregisterConfigurationCallback_alreadyUnregistered_throwsIllegalArgumentException()
+ throws Exception {
+ grantPermissions(THREAD_NETWORK_PRIVILEGED);
+
+ Consumer<ThreadConfiguration> callback = config -> {};
+ registerConfigurationCallback(mController, mExecutor, callback);
+ mController.unregisterConfigurationCallback(callback);
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mController.unregisterConfigurationCallback(callback));
+ }
+
private void grantPermissions(String... permissions) {
for (String permission : permissions) {
mGrantedPermissions.add(permission);
@@ -1037,6 +1206,35 @@
}
}
+ private class ConfigurationListener {
+ private ArrayTrackRecord<ThreadConfiguration> mConfigurations = new ArrayTrackRecord<>();
+ private final ArrayTrackRecord<ThreadConfiguration>.ReadHead mReadHead =
+ mConfigurations.newReadHead();
+ ThreadNetworkController mController;
+ Consumer<ThreadConfiguration> mCallback = (config) -> mConfigurations.add(config);
+
+ ConfigurationListener(ThreadNetworkController controller) {
+ this.mController = controller;
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> controller.registerConfigurationCallback(mExecutor, mCallback));
+ }
+
+ public void expectConfiguration(ThreadConfiguration config) {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> c.equals(config))).isNotNull();
+ }
+
+ public void expectNoMoreConfiguration() {
+ assertThat(mReadHead.poll(CALLBACK_TIMEOUT_MILLIS, c -> true)).isNull();
+ }
+
+ public void unregisterConfigurationCallback() {
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () -> mController.unregisterConfigurationCallback(mCallback));
+ }
+ }
+
private int booleanToEnabledState(boolean enabled) {
return enabled ? STATE_ENABLED : STATE_DISABLED;
}
@@ -1051,6 +1249,18 @@
waitForEnabledState(controller, booleanToEnabledState(enabled));
}
+ private void setConfigurationAndWait(
+ ThreadNetworkController controller, ThreadConfiguration configuration)
+ throws Exception {
+ CompletableFuture<Void> setFuture = new CompletableFuture<>();
+ runAsShell(
+ THREAD_NETWORK_PRIVILEGED,
+ () ->
+ controller.setConfiguration(
+ configuration, mExecutor, newOutcomeReceiver(setFuture)));
+ setFuture.get(SET_CONFIGURATION_TIMEOUT_MILLIS, MILLISECONDS);
+ }
+
private CompletableFuture joinRandomizedDataset(
ThreadNetworkController controller, String networkName) throws Exception {
ActiveOperationalDataset activeDataset = newRandomizedDataset(networkName, controller);
@@ -1117,6 +1327,14 @@
};
}
+ private void registerConfigurationCallback(
+ ThreadNetworkController controller,
+ Executor executor,
+ Consumer<ThreadConfiguration> callback) {
+ controller.registerConfigurationCallback(executor, callback);
+ mConfigurationCallbacksToCleanUp.add(callback);
+ }
+
private static void assertDoesNotThrow(ThrowingRunnable runnable) {
try {
runnable.run();
diff --git a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
index 8c63d37..9e8dc3a 100644
--- a/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
+++ b/thread/tests/integration/src/android/net/thread/BorderRoutingTest.java
@@ -17,23 +17,26 @@
package android.net.thread;
import static android.Manifest.permission.MANAGE_TEST_NETWORKS;
-import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
+import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.thread.utils.IntegrationTestUtils.DEFAULT_DATASET;
import static android.net.thread.utils.IntegrationTestUtils.getIpv6LinkAddresses;
+import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv4Packet;
import static android.net.thread.utils.IntegrationTestUtils.isExpectedIcmpv6Packet;
-import static android.net.thread.utils.IntegrationTestUtils.isFromIpv6Source;
+import static android.net.thread.utils.IntegrationTestUtils.isFrom;
import static android.net.thread.utils.IntegrationTestUtils.isInMulticastGroup;
-import static android.net.thread.utils.IntegrationTestUtils.isToIpv6Destination;
+import static android.net.thread.utils.IntegrationTestUtils.isTo;
+import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr;
import static android.net.thread.utils.IntegrationTestUtils.newPacketReader;
import static android.net.thread.utils.IntegrationTestUtils.pollForPacket;
import static android.net.thread.utils.IntegrationTestUtils.sendUdpMessage;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
+import static android.system.OsConstants.ICMP_ECHO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REPLY_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork;
import static com.android.testutils.TestPermissionUtil.runAsShell;
-import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -44,11 +47,11 @@
import static java.util.Objects.requireNonNull;
import android.content.Context;
-import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
+import android.net.RouteInfo;
import android.net.thread.utils.FullThreadDevice;
import android.net.thread.utils.InfraNetworkDevice;
import android.net.thread.utils.OtDaemonController;
@@ -74,7 +77,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
import java.net.Inet6Address;
+import java.net.InetAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -89,22 +94,14 @@
private static final String TAG = BorderRoutingTest.class.getSimpleName();
private static final int NUM_FTD = 2;
private static final Inet6Address GROUP_ADDR_SCOPE_5 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+ (Inet6Address) parseNumericAddress("ff05::1234");
private static final Inet6Address GROUP_ADDR_SCOPE_4 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff04::1234");
+ (Inet6Address) parseNumericAddress("ff04::1234");
private static final Inet6Address GROUP_ADDR_SCOPE_3 =
- (Inet6Address) InetAddresses.parseNumericAddress("ff03::1234");
-
- // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
- private static final byte[] DEFAULT_DATASET_TLVS =
- base16().decode(
- "0E080000000000010000000300001335060004001FFFE002"
- + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
- + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
- + "642D643961300102D9A00410A245479C836D551B9CA557F7"
- + "B9D351B40C0402A0FFF8");
- private static final ActiveOperationalDataset DEFAULT_DATASET =
- ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+ (Inet6Address) parseNumericAddress("ff03::1234");
+ private static final Inet4Address IPV4_SERVER_ADDR =
+ (Inet4Address) parseNumericAddress("8.8.8.8");
+ private static final String NAT64_CIDR = "192.168.255.0/24";
@Rule public final ThreadFeatureCheckerRule mThreadRule = new ThreadFeatureCheckerRule();
@@ -171,12 +168,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
// Infra device receives an echo reply sent by FTD.
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -193,11 +190,11 @@
startInfraDeviceAndWaitForOnLinkAddr();
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -213,7 +210,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
// Create a new infra network and let Thread prefer it
TestNetworkTracker oldInfraNetworkTracker = mInfraNetworkTracker;
@@ -224,7 +221,7 @@
mInfraDevice.sendEchoRequest(ftd.getOmrAddress());
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
} finally {
runAsShell(MANAGE_TEST_NETWORKS, () -> oldInfraNetworkTracker.teardown());
}
@@ -243,7 +240,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = requireNonNull(ftd.getOmrAddress());
Inet6Address ftdMlEid = requireNonNull(ftd.getMlEid());
@@ -285,7 +282,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_5);
@@ -307,7 +304,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
@@ -328,12 +325,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -360,12 +357,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.subscribeMulticastAddress(GROUP_ADDR_SCOPE_3);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_3);
- assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -382,11 +379,11 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
- assertNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
+ assertNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd.getOmrAddress()));
}
@Test
@@ -407,22 +404,24 @@
*/
FullThreadDevice ftd1 = mFtds.get(0);
- startFtdChild(ftd1);
+ joinNetworkAndWaitForOmr(ftd1, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
FullThreadDevice ftd2 = mFtds.get(1);
- startFtdChild(ftd2);
+ joinNetworkAndWaitForOmr(ftd2, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_4);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
// Verify ping reply from ftd1 and ftd2 separately as the order of replies can't be
// predicted.
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_4);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
}
@Test
@@ -443,21 +442,23 @@
*/
FullThreadDevice ftd1 = mFtds.get(0);
- startFtdChild(ftd1);
+ joinNetworkAndWaitForOmr(ftd1, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd1, GROUP_ADDR_SCOPE_5);
FullThreadDevice ftd2 = mFtds.get(1);
- startFtdChild(ftd2);
+ joinNetworkAndWaitForOmr(ftd2, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd2, GROUP_ADDR_SCOPE_5);
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd1.getOmrAddress()));
// Send the request twice as the order of replies from ftd1 and ftd2 are not guaranteed
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
+ assertNotNull(
+ pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftd2.getOmrAddress()));
}
@Test
@@ -473,16 +474,18 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
ftd.ping(GROUP_ADDR_SCOPE_5);
ftd.ping(GROUP_ADDR_SCOPE_4);
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_5));
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
}
@Test
@@ -499,12 +502,12 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.ping(GROUP_ADDR_SCOPE_3);
assertNull(
- pollForPacketOnInfraNetwork(
+ pollForIcmpPacketOnInfraNetwork(
ICMPV6_ECHO_REQUEST_TYPE, ftd.getOmrAddress(), GROUP_ADDR_SCOPE_3));
}
@@ -521,14 +524,15 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdLla = ftd.getLinkLocalAddress();
assertNotNull(ftdLla);
ftd.ping(GROUP_ADDR_SCOPE_4, ftdLla);
assertNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdLla, GROUP_ADDR_SCOPE_4));
}
@Test
@@ -544,7 +548,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
List<Inet6Address> ftdMlas = ftd.getMeshLocalAddresses();
assertFalse(ftdMlas.isEmpty());
@@ -552,7 +556,7 @@
ftd.ping(GROUP_ADDR_SCOPE_4, ftdMla);
assertNull(
- pollForPacketOnInfraNetwork(
+ pollForIcmpPacketOnInfraNetwork(
ICMPV6_ECHO_REQUEST_TYPE, ftdMla, GROUP_ADDR_SCOPE_4));
}
}
@@ -571,7 +575,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
subscribeMulticastAddressAndWait(ftd, GROUP_ADDR_SCOPE_5);
Inet6Address ftdOmr = ftd.getOmrAddress();
@@ -583,7 +587,7 @@
mInfraDevice.sendEchoRequest(GROUP_ADDR_SCOPE_5);
- assertNotNull(pollForPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMPV6_ECHO_REPLY_TYPE, ftdOmr));
}
@Test
@@ -599,7 +603,7 @@
*/
FullThreadDevice ftd = mFtds.get(0);
- startFtdChild(ftd);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
Inet6Address ftdOmr = ftd.getOmrAddress();
// Destroy infra link and re-create
@@ -611,33 +615,46 @@
ftd.ping(GROUP_ADDR_SCOPE_4);
assertNotNull(
- pollForPacketOnInfraNetwork(ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ pollForIcmpPacketOnInfraNetwork(
+ ICMPV6_ECHO_REQUEST_TYPE, ftdOmr, GROUP_ADDR_SCOPE_4));
+ }
+
+ @Test
+ public void nat64_threadDevicePingIpv4InfraDevice_outboundPacketIsForwarded() throws Exception {
+ FullThreadDevice ftd = mFtds.get(0);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
+ // TODO: enable NAT64 via ThreadNetworkController API instead of ot-ctl
+ mOtCtl.setNat64Cidr(NAT64_CIDR);
+ mOtCtl.setNat64Enabled(true);
+ waitFor(() -> mOtCtl.hasNat64PrefixInNetdata(), Duration.ofSeconds(10));
+
+ ftd.ping(IPV4_SERVER_ADDR);
+
+ assertNotNull(pollForIcmpPacketOnInfraNetwork(ICMP_ECHO, null, IPV4_SERVER_ADDR));
}
private void setUpInfraNetwork() throws Exception {
+ LinkProperties lp = new LinkProperties();
+ // NAT64 feature requires the infra network to have an IPv4 default route.
+ lp.addRoute(
+ new RouteInfo(
+ new IpPrefix("0.0.0.0/0") /* destination */,
+ null /* gateway */,
+ null,
+ RouteInfo.RTN_UNICAST,
+ 1500 /* mtu */));
mInfraNetworkTracker =
runAsShell(
MANAGE_TEST_NETWORKS,
- () ->
- initTestNetwork(
- mContext, new LinkProperties(), 5000 /* timeoutMs */));
- mController.setTestNetworkAsUpstreamAndWait(
- mInfraNetworkTracker.getTestIface().getInterfaceName());
+ () -> initTestNetwork(mContext, lp, 5000 /* timeoutMs */));
+ String infraNetworkName = mInfraNetworkTracker.getTestIface().getInterfaceName();
+ mController.setTestNetworkAsUpstreamAndWait(infraNetworkName);
}
private void tearDownInfraNetwork() {
runAsShell(MANAGE_TEST_NETWORKS, () -> mInfraNetworkTracker.teardown());
}
- private void startFtdChild(FullThreadDevice ftd) throws Exception {
- ftd.factoryReset();
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
- waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
- Inet6Address ftdOmr = ftd.getOmrAddress();
- assertNotNull(ftdOmr);
- }
-
private void startInfraDeviceAndWaitForOnLinkAddr() throws Exception {
mInfraDevice =
new InfraNetworkDevice(MacAddress.fromString("1:2:3:4:5:6"), mInfraNetworkReader);
@@ -668,20 +685,28 @@
assertInfraLinkMemberOfGroup(address);
}
- private byte[] pollForPacketOnInfraNetwork(int type, Inet6Address srcAddress) {
- return pollForPacketOnInfraNetwork(type, srcAddress, null);
+ private byte[] pollForIcmpPacketOnInfraNetwork(int type, InetAddress srcAddress) {
+ return pollForIcmpPacketOnInfraNetwork(type, srcAddress, null /* destAddress */);
}
- private byte[] pollForPacketOnInfraNetwork(
- int type, Inet6Address srcAddress, Inet6Address destAddress) {
- Predicate<byte[]> filter;
- filter =
+ private byte[] pollForIcmpPacketOnInfraNetwork(
+ int type, InetAddress srcAddress, InetAddress destAddress) {
+ if (srcAddress == null && destAddress == null) {
+ throw new IllegalArgumentException("srcAddress and destAddress cannot be both null");
+ }
+ if (srcAddress != null && destAddress != null) {
+ if ((srcAddress instanceof Inet4Address) != (destAddress instanceof Inet4Address)) {
+ throw new IllegalArgumentException(
+ "srcAddress and destAddress must be both IPv4 or both IPv6");
+ }
+ }
+ boolean isIpv4 =
+ (srcAddress instanceof Inet4Address) || (destAddress instanceof Inet4Address);
+ final Predicate<byte[]> filter =
p ->
- (isExpectedIcmpv6Packet(p, type)
- && (srcAddress == null ? true : isFromIpv6Source(p, srcAddress))
- && (destAddress == null
- ? true
- : isToIpv6Destination(p, destAddress)));
+ (isIpv4 ? isExpectedIcmpv4Packet(p, type) : isExpectedIcmpv6Packet(p, type))
+ && (srcAddress == null || isFrom(p, srcAddress))
+ && (destAddress == null || isTo(p, destAddress));
return pollForPacket(mInfraNetworkReader, filter);
}
}
diff --git a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
index e10f134..2afca5f 100644
--- a/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
+++ b/thread/tests/integration/src/android/net/thread/ServiceDiscoveryTest.java
@@ -18,10 +18,10 @@
import static android.net.InetAddresses.parseNumericAddress;
import static android.net.nsd.NsdManager.PROTOCOL_DNS_SD;
-import static android.net.thread.utils.IntegrationTestUtils.JOIN_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.SERVICE_DISCOVERY_TIMEOUT;
import static android.net.thread.utils.IntegrationTestUtils.discoverForServiceLost;
import static android.net.thread.utils.IntegrationTestUtils.discoverService;
+import static android.net.thread.utils.IntegrationTestUtils.joinNetworkAndWaitForOmr;
import static android.net.thread.utils.IntegrationTestUtils.resolveService;
import static android.net.thread.utils.IntegrationTestUtils.resolveServiceUntil;
import static android.net.thread.utils.IntegrationTestUtils.waitFor;
@@ -168,8 +168,7 @@
// Creates Full Thread Devices (FTD) and let them join the network.
for (FullThreadDevice ftd : mFtds) {
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
}
int randomId = new Random().nextInt(10_000);
@@ -223,8 +222,7 @@
// Creates a Full Thread Devices (FTD) and let it join the network.
FullThreadDevice ftd = mFtds.get(0);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setSrpHostname("my-host");
ftd.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001:db8::1")));
ftd.addSrpService(
@@ -279,8 +277,7 @@
// Creates a Full Thread Devices (FTD) and let it join the network.
FullThreadDevice ftd = mFtds.get(0);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setSrpHostname("my-host");
ftd.setSrpHostAddresses(
List.of(
@@ -346,8 +343,7 @@
mRegistrationListeners.add(listener);
for (int i = 0; i < NUM_FTD; ++i) {
FullThreadDevice ftd = mFtds.get(i);
- ftd.joinNetwork(DEFAULT_DATASET);
- ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(ftd, DEFAULT_DATASET);
ftd.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
}
final ArrayList<NsdServiceInfo> browsedServices = new ArrayList<>();
@@ -409,8 +405,7 @@
* </pre>
*/
FullThreadDevice srpClient = mFtds.get(0);
- srpClient.joinNetwork(DEFAULT_DATASET);
- srpClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(srpClient, DEFAULT_DATASET);
srpClient.setSrpHostname("my-host");
srpClient.setSrpHostAddresses(List.of((Inet6Address) parseNumericAddress("2001::1")));
srpClient.addSrpService(
@@ -421,8 +416,7 @@
Map.of("key1", bytes(0x01, 0x02), "key2", bytes(0x03)));
FullThreadDevice dnsClient = mFtds.get(1);
- dnsClient.joinNetwork(DEFAULT_DATASET);
- dnsClient.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ joinNetworkAndWaitForOmr(dnsClient, DEFAULT_DATASET);
dnsClient.setDnsServerAddress(mOtCtl.getMlEid().getHostAddress());
NsdServiceInfo browsedService = dnsClient.browseService("_test._udp.default.service.arpa.");
diff --git a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
index c0a8eea..083a841 100644
--- a/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
+++ b/thread/tests/integration/src/android/net/thread/utils/FullThreadDevice.java
@@ -282,6 +282,7 @@
for (String subtype : subtypes) {
fullServiceType.append(",").append(subtype);
}
+ waitForSrpServer();
executeCommand(
"srp client service add %s %s %d %d %d %s",
serviceName,
@@ -416,7 +417,7 @@
executeCommand("ipmaddr add " + address.getHostAddress());
}
- public void ping(Inet6Address address, Inet6Address source) {
+ public void ping(InetAddress address, Inet6Address source) {
ping(
address,
source,
@@ -427,7 +428,7 @@
PING_TIMEOUT_0_1_SECOND);
}
- public void ping(Inet6Address address) {
+ public void ping(InetAddress address) {
ping(
address,
null,
@@ -439,7 +440,7 @@
}
/** Returns the number of ping reply packets received. */
- public int ping(Inet6Address address, int count) {
+ public int ping(InetAddress address, int count) {
List<String> output =
ping(
address,
@@ -453,7 +454,7 @@
}
private List<String> ping(
- Inet6Address address,
+ InetAddress address,
Inet6Address source,
int size,
int count,
@@ -492,6 +493,22 @@
return -1;
}
+ /** Waits for an SRP server to be present in Network Data */
+ private void waitForSrpServer() throws TimeoutException {
+ // CLI output:
+ // > srp client server
+ // [fd64:db12:25f4:7e0b:1bfc:6344:25ac:2dd7]:53538
+ // Done
+ waitFor(
+ () -> {
+ final String serverAddr = executeCommand("srp client server").get(0);
+ final int lastColonIndex = serverAddr.lastIndexOf(':');
+ final int port = Integer.parseInt(serverAddr.substring(lastColonIndex + 1));
+ return port > 0;
+ },
+ SERVICE_DISCOVERY_TIMEOUT);
+ }
+
@FormatMethod
private List<String> executeCommand(String commandFormat, Object... args) {
return executeCommand(String.format(commandFormat, args));
diff --git a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
index ada46c8..82e9332 100644
--- a/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
+++ b/thread/tests/integration/src/android/net/thread/utils/IntegrationTestUtils.java
@@ -16,14 +16,18 @@
package android.net.thread.utils;
import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK;
+import static android.system.OsConstants.IPPROTO_ICMP;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
+import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
+import static org.junit.Assert.assertNotNull;
+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
@@ -36,6 +40,7 @@
import android.net.TestNetworkInterface;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ThreadNetworkController;
import android.os.Build;
import android.os.Handler;
@@ -45,7 +50,9 @@
import androidx.test.core.app.ApplicationProvider;
import com.android.net.module.util.Struct;
+import com.android.net.module.util.structs.Icmpv4Header;
import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv4Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RaHeader;
@@ -58,6 +65,7 @@
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
+import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -84,6 +92,17 @@
public static final Duration CALLBACK_TIMEOUT = Duration.ofSeconds(1);
public static final Duration SERVICE_DISCOVERY_TIMEOUT = Duration.ofSeconds(20);
+ // A valid Thread Active Operational Dataset generated from OpenThread CLI "dataset init new".
+ private static final byte[] DEFAULT_DATASET_TLVS =
+ base16().decode(
+ "0E080000000000010000000300001335060004001FFFE002"
+ + "08ACC214689BC40BDF0708FD64DB1225F47E0B0510F26B31"
+ + "53760F519A63BAFDDFFC80D2AF030F4F70656E5468726561"
+ + "642D643961300102D9A00410A245479C836D551B9CA557F7"
+ + "B9D351B40C0402A0FFF8");
+ public static final ActiveOperationalDataset DEFAULT_DATASET =
+ ActiveOperationalDataset.fromThreadTlvs(DEFAULT_DATASET_TLVS);
+
private IntegrationTestUtils() {}
/**
@@ -177,16 +196,36 @@
return null;
}
- /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
- public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
- if (packet == null) {
+ /** Returns {@code true} if {@code packet} is an ICMPv4 packet of given {@code type}. */
+ public static boolean isExpectedIcmpv4Packet(byte[] packet, int type) {
+ ByteBuffer buf = makeByteBuffer(packet);
+ Ipv4Header header = extractIpv4Header(buf);
+ if (header == null) {
return false;
}
- ByteBuffer buf = ByteBuffer.wrap(packet);
+ if (header.protocol != (byte) IPPROTO_ICMP) {
+ return false;
+ }
try {
- if (Struct.parse(Ipv6Header.class, buf).nextHeader != (byte) IPPROTO_ICMPV6) {
- return false;
- }
+ return Struct.parse(Icmpv4Header.class, buf).type == (short) type;
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return false;
+ }
+
+ /** Returns {@code true} if {@code packet} is an ICMPv6 packet of given {@code type}. */
+ public static boolean isExpectedIcmpv6Packet(byte[] packet, int type) {
+ ByteBuffer buf = makeByteBuffer(packet);
+ Ipv6Header header = extractIpv6Header(buf);
+ if (header == null) {
+ return false;
+ }
+ if (header.nextHeader != (byte) IPPROTO_ICMPV6) {
+ return false;
+ }
+ try {
return Struct.parse(Icmpv6Header.class, buf).type == (short) type;
} catch (IllegalArgumentException ignored) {
// It's fine that the passed in packet is malformed because it's could be sent
@@ -195,32 +234,66 @@
return false;
}
- public static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
- if (packet == null) {
- return false;
- }
- ByteBuffer buf = ByteBuffer.wrap(packet);
- try {
- return Struct.parse(Ipv6Header.class, buf).srcIp.equals(src);
- } catch (IllegalArgumentException ignored) {
- // It's fine that the passed in packet is malformed because it's could be sent
- // by anybody.
+ public static boolean isFrom(byte[] packet, InetAddress src) {
+ if (src instanceof Inet4Address) {
+ return isFromIpv4Source(packet, (Inet4Address) src);
+ } else if (src instanceof Inet6Address) {
+ return isFromIpv6Source(packet, (Inet6Address) src);
}
return false;
}
- public static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
- if (packet == null) {
- return false;
+ public static boolean isTo(byte[] packet, InetAddress dest) {
+ if (dest instanceof Inet4Address) {
+ return isToIpv4Destination(packet, (Inet4Address) dest);
+ } else if (dest instanceof Inet6Address) {
+ return isToIpv6Destination(packet, (Inet6Address) dest);
}
- ByteBuffer buf = ByteBuffer.wrap(packet);
+ return false;
+ }
+
+ private static boolean isFromIpv4Source(byte[] packet, Inet4Address src) {
+ Ipv4Header header = extractIpv4Header(makeByteBuffer(packet));
+ return header != null && header.srcIp.equals(src);
+ }
+
+ private static boolean isFromIpv6Source(byte[] packet, Inet6Address src) {
+ Ipv6Header header = extractIpv6Header(makeByteBuffer(packet));
+ return header != null && header.srcIp.equals(src);
+ }
+
+ private static boolean isToIpv4Destination(byte[] packet, Inet4Address dest) {
+ Ipv4Header header = extractIpv4Header(makeByteBuffer(packet));
+ return header != null && header.dstIp.equals(dest);
+ }
+
+ private static boolean isToIpv6Destination(byte[] packet, Inet6Address dest) {
+ Ipv6Header header = extractIpv6Header(makeByteBuffer(packet));
+ return header != null && header.dstIp.equals(dest);
+ }
+
+ private static ByteBuffer makeByteBuffer(byte[] packet) {
+ return packet == null ? null : ByteBuffer.wrap(packet);
+ }
+
+ private static Ipv4Header extractIpv4Header(ByteBuffer buf) {
try {
- return Struct.parse(Ipv6Header.class, buf).dstIp.equals(dest);
+ return Struct.parse(Ipv4Header.class, buf);
} catch (IllegalArgumentException ignored) {
// It's fine that the passed in packet is malformed because it's could be sent
// by anybody.
}
- return false;
+ return null;
+ }
+
+ private static Ipv6Header extractIpv6Header(ByteBuffer buf) {
+ try {
+ return Struct.parse(Ipv6Header.class, buf);
+ } catch (IllegalArgumentException ignored) {
+ // It's fine that the passed in packet is malformed because it's could be sent
+ // by anybody.
+ }
+ return null;
}
/** Returns the Prefix Information Options (PIO) extracted from an ICMPv6 RA message. */
@@ -413,6 +486,22 @@
return networkFuture.get(timeout.toSeconds(), SECONDS);
}
+ /**
+ * Let the FTD join the specified Thread network and wait for border routing to be available.
+ *
+ * @return the OMR address
+ */
+ public static Inet6Address joinNetworkAndWaitForOmr(
+ FullThreadDevice ftd, ActiveOperationalDataset dataset) throws Exception {
+ ftd.factoryReset();
+ ftd.joinNetwork(dataset);
+ ftd.waitForStateAnyOf(List.of("router", "child"), JOIN_TIMEOUT);
+ waitFor(() -> ftd.getOmrAddress() != null, Duration.ofSeconds(60));
+ Inet6Address ftdOmr = ftd.getOmrAddress();
+ assertNotNull(ftdOmr);
+ return ftdOmr;
+ }
+
private static class DefaultDiscoveryListener implements NsdManager.DiscoveryListener {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
diff --git a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
index b3175fd..15a3f5c 100644
--- a/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
+++ b/thread/tests/integration/src/android/net/thread/utils/OtDaemonController.java
@@ -105,6 +105,29 @@
return prefixes.isEmpty() ? null : prefixes.get(0);
}
+ /** Enables/Disables NAT64 feature. */
+ public void setNat64Enabled(boolean enabled) {
+ executeCommand("nat64 " + (enabled ? "enable" : "disable"));
+ }
+
+ /** Sets the NAT64 CIDR. */
+ public void setNat64Cidr(String cidr) {
+ executeCommand("nat64 cidr " + cidr);
+ }
+
+ /** Returns whether there's a NAT64 prefix in network data */
+ public boolean hasNat64PrefixInNetdata() {
+ // Example (in the 'Routes' section):
+ // fdb2:bae3:5b59:2:0:0::/96 sn low c000
+ List<String> outputLines = executeCommandAndParse("netdata show");
+ for (String line : outputLines) {
+ if (line.contains(" sn")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public String executeCommand(String cmd) {
return SystemUtil.runShellCommand(OT_CTL + " " + cmd);
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
index eaf11b1..be32764 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadNetworkControllerServiceTest.java
@@ -16,6 +16,12 @@
package com.android.server.thread;
+import static android.Manifest.permission.NETWORK_SETTINGS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
+import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_THREAD;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.thread.ActiveOperationalDataset.CHANNEL_PAGE_24_GHZ;
import static android.net.thread.ThreadNetworkController.STATE_DISABLED;
import static android.net.thread.ThreadNetworkController.STATE_ENABLED;
@@ -38,6 +44,8 @@
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
@@ -57,6 +65,7 @@
import android.net.ConnectivityManager;
import android.net.NetworkAgent;
import android.net.NetworkProvider;
+import android.net.NetworkRequest;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.IActiveOperationalDatasetReceiver;
import android.net.thread.IOperationReceiver;
@@ -181,6 +190,9 @@
.when(mContext)
.enforceCallingOrSelfPermission(
eq(PERMISSION_THREAD_NETWORK_PRIVILEGED), anyString());
+ doNothing()
+ .when(mContext)
+ .enforceCallingOrSelfPermission(eq(NETWORK_SETTINGS), anyString());
mTestLooper = new TestLooper();
final Handler handler = new Handler(mTestLooper.getLooper());
@@ -716,12 +728,12 @@
ThreadConfiguration config1 =
new ThreadConfiguration.Builder()
.setNat64Enabled(false)
- .setDhcp6PdEnabled(false)
+ .setDhcpv6PdEnabled(false)
.build();
ThreadConfiguration config2 =
new ThreadConfiguration.Builder()
.setNat64Enabled(true)
- .setDhcp6PdEnabled(true)
+ .setDhcpv6PdEnabled(true)
.build();
ThreadConfiguration config3 =
new ThreadConfiguration.Builder(config2).build(); // Same as config2
@@ -737,4 +749,56 @@
inOrder.verify(mockReceiver2).onSuccess();
inOrder.verify(mockReceiver3).onSuccess();
}
+
+ @Test
+ public void initialize_upstreamNetworkRequestHasCertainTransportTypesAndCapabilities() {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mMockConnectivityManager, atLeastOnce())
+ .registerNetworkCallback(
+ networkRequestCaptor.capture(),
+ any(ConnectivityManager.NetworkCallback.class),
+ any(Handler.class));
+ List<NetworkRequest> upstreamNetworkRequests =
+ networkRequestCaptor.getAllValues().stream()
+ .filter(nr -> !nr.hasTransport(TRANSPORT_THREAD))
+ .toList();
+ assertThat(upstreamNetworkRequests.size()).isEqualTo(1);
+ NetworkRequest upstreamNetworkRequest = upstreamNetworkRequests.get(0);
+ assertThat(upstreamNetworkRequest.hasTransport(TRANSPORT_WIFI)).isTrue();
+ assertThat(upstreamNetworkRequest.hasTransport(TRANSPORT_ETHERNET)).isTrue();
+ assertThat(upstreamNetworkRequest.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ assertThat(upstreamNetworkRequest.hasCapability(NET_CAPABILITY_INTERNET)).isTrue();
+ }
+
+ @Test
+ public void setTestNetworkAsUpstream_upstreamNetworkRequestAlwaysDisallowsVpn() {
+ mService.initialize();
+ mTestLooper.dispatchAll();
+ clearInvocations(mMockConnectivityManager);
+
+ final IOperationReceiver mockReceiver1 = mock(IOperationReceiver.class);
+ final IOperationReceiver mockReceiver2 = mock(IOperationReceiver.class);
+ mService.setTestNetworkAsUpstream("test-network", mockReceiver1);
+ mService.setTestNetworkAsUpstream(null, mockReceiver2);
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<NetworkRequest> networkRequestCaptor =
+ ArgumentCaptor.forClass(NetworkRequest.class);
+ verify(mMockConnectivityManager, times(2))
+ .registerNetworkCallback(
+ networkRequestCaptor.capture(),
+ any(ConnectivityManager.NetworkCallback.class),
+ any(Handler.class));
+ assertThat(networkRequestCaptor.getAllValues().size()).isEqualTo(2);
+ NetworkRequest networkRequest1 = networkRequestCaptor.getAllValues().get(0);
+ NetworkRequest networkRequest2 = networkRequestCaptor.getAllValues().get(1);
+ assertThat(networkRequest1.getNetworkSpecifier()).isNotNull();
+ assertThat(networkRequest1.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ assertThat(networkRequest2.getNetworkSpecifier()).isNull();
+ assertThat(networkRequest2.hasCapability(NET_CAPABILITY_NOT_VPN)).isTrue();
+ }
}
diff --git a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
index c932ac8..ba489d9 100644
--- a/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
+++ b/thread/tests/unit/src/com/android/server/thread/ThreadPersistentSettingsTest.java
@@ -152,7 +152,7 @@
ThreadConfiguration configuration =
new ThreadConfiguration.Builder()
.setNat64Enabled(true)
- .setDhcp6PdEnabled(true)
+ .setDhcpv6PdEnabled(true)
.build();
mThreadPersistentSettings.putConfiguration(configuration);
@@ -164,13 +164,13 @@
ThreadConfiguration configuration1 =
new ThreadConfiguration.Builder()
.setNat64Enabled(false)
- .setDhcp6PdEnabled(false)
+ .setDhcpv6PdEnabled(false)
.build();
mThreadPersistentSettings.putConfiguration(configuration1);
ThreadConfiguration configuration2 =
new ThreadConfiguration.Builder()
.setNat64Enabled(true)
- .setDhcp6PdEnabled(true)
+ .setDhcpv6PdEnabled(true)
.build();
assertThat(mThreadPersistentSettings.putConfiguration(configuration2)).isTrue();
@@ -188,9 +188,9 @@
}
@Test
- public void putConfiguration_dhcp6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
+ public void putConfiguration_dhcpv6PdEnabled_valuesUpdatedAndPersisted() throws Exception {
ThreadConfiguration configuration =
- new ThreadConfiguration.Builder().setDhcp6PdEnabled(true).build();
+ new ThreadConfiguration.Builder().setDhcpv6PdEnabled(true).build();
mThreadPersistentSettings.putConfiguration(configuration);
assertThat(mThreadPersistentSettings.getConfiguration()).isEqualTo(configuration);