Merge "BpfSyscallWrappers: grab shared lock on writable map open" into main
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 304a6ed..8ed5ac0 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -112,7 +112,7 @@
],
prebuilts: [
"current_sdkinfo",
- "netbpfload.mainline.rc",
+ "netbpfload.33rc",
"netbpfload.35rc",
"ot-daemon.init.34rc",
],
diff --git a/bpf_progs/block.c b/bpf_progs/block.c
index 152dda6..e467203 100644
--- a/bpf_progs/block.c
+++ b/bpf_progs/block.c
@@ -20,7 +20,7 @@
#include <stdint.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
diff --git a/bpf_progs/clatd.c b/bpf_progs/clatd.c
index f83e5ae..9e0d4c4 100644
--- a/bpf_progs/clatd.c
+++ b/bpf_progs/clatd.c
@@ -31,7 +31,7 @@
#include <linux/udp.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
#include "bpf_net_helpers.h"
diff --git a/bpf_progs/dscpPolicy.c b/bpf_progs/dscpPolicy.c
index ed114e4..1739c37 100644
--- a/bpf_progs/dscpPolicy.c
+++ b/bpf_progs/dscpPolicy.c
@@ -28,7 +28,7 @@
#include <string.h>
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include "bpf_helpers.h"
#include "dscpPolicy.h"
diff --git a/bpf_progs/netd.c b/bpf_progs/netd.c
index 76ab329..a6d702b 100644
--- a/bpf_progs/netd.c
+++ b/bpf_progs/netd.c
@@ -15,7 +15,7 @@
*/
// The resulting .o needs to load on Android T+
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_T_VERSION
#include <bpf_helpers.h>
#include <linux/bpf.h>
@@ -106,13 +106,13 @@
// A single-element configuration array, packet tracing is enabled when 'true'.
DEFINE_BPF_MAP_EXT(packet_trace_enabled_map, ARRAY, uint32_t, bool, 1,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG)
// A ring buffer on which packet information is pushed.
DEFINE_BPF_RINGBUF_EXT(packet_trace_ringbuf, PacketTrace, PACKET_TRACE_BUF_SIZE,
AID_ROOT, AID_SYSTEM, 0060, "fs_bpf_net_shared", "", PRIVATE,
- BPFLOADER_MAINLINE_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, LOAD_ON_ENG,
LOAD_ON_USER, LOAD_ON_USERDEBUG);
DEFINE_BPF_MAP_RO_NETD(data_saver_enabled_map, ARRAY, uint32_t, bool,
@@ -522,7 +522,7 @@
// 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,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -532,7 +532,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
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,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -554,7 +554,7 @@
// 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,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, OPTIONAL,
"fs_bpf_netd_readonly", "",
IGNORE_ON_ENG, LOAD_ON_USER, IGNORE_ON_USERDEBUG)
(struct __sk_buff* skb) {
@@ -564,7 +564,7 @@
// This program is required, and enables tracing on Android U+, 5.8+, userdebug/eng.
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,
+ BPFLOADER_U_VERSION, BPFLOADER_MAX_VER, MANDATORY,
"fs_bpf_netd_readonly", "",
LOAD_ON_ENG, IGNORE_ON_USER, LOAD_ON_USERDEBUG)
(struct __sk_buff* skb) {
diff --git a/bpf_progs/offload.c b/bpf_progs/offload.c
index 4f152bf..8c3011b 100644
--- a/bpf_progs/offload.c
+++ b/bpf_progs/offload.c
@@ -28,11 +28,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
#else /* MAINLINE */
-// The resulting .o needs to load on the Android S bpfloader
+// The resulting .o needs to load on the Android S & T bpfloaders
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
diff --git a/bpf_progs/test.c b/bpf_progs/test.c
index fff3512..2b4a08f 100644
--- a/bpf_progs/test.c
+++ b/bpf_progs/test.c
@@ -22,11 +22,11 @@
// BTF is incompatible with bpfloaders < v0.10, hence for S (v0.2) we must
// ship a different file than for later versions, but we need bpfloader v0.25+
// for obj@ver.o support
-#define BPFLOADER_MIN_VER BPFLOADER_MAINLINE_T_VERSION
+#define BPFLOADER_MIN_VER BPFLOADER_OBJ_AT_VER_VERSION
#else /* MAINLINE */
-// The resulting .o needs to load on the Android S bpfloader
+// The resulting .o needs to load on the Android S & T bpfloaders
#define BPFLOADER_MIN_VER BPFLOADER_S_VERSION
-#define BPFLOADER_MAX_VER BPFLOADER_T_VERSION
+#define BPFLOADER_MAX_VER BPFLOADER_OBJ_AT_VER_VERSION
#endif /* MAINLINE */
// Warning: values other than AID_ROOT don't work for map uid on BpfLoader < v0.21
diff --git a/framework/src/android/net/apf/ApfCapabilities.java b/framework/src/android/net/apf/ApfCapabilities.java
index 6b18629..f92cdbb 100644
--- a/framework/src/android/net/apf/ApfCapabilities.java
+++ b/framework/src/android/net/apf/ApfCapabilities.java
@@ -106,6 +106,8 @@
@Override
public int hashCode() {
+ // hashCode it is not implemented in R. Therefore it would be dangerous for
+ // NetworkStack to depend on it.
return Objects.hash(apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
}
diff --git a/netbpfload/Android.bp b/netbpfload/Android.bp
index f278695..908bb13 100644
--- a/netbpfload/Android.bp
+++ b/netbpfload/Android.bp
@@ -70,8 +70,8 @@
// For details of versioned rc files see:
// https://android.googlesource.com/platform/system/core/+/HEAD/init/README.md#versioned-rc-files-within-apexs
prebuilt_etc {
- name: "netbpfload.mainline.rc",
- src: "netbpfload.mainline.rc",
+ name: "netbpfload.33rc",
+ src: "netbpfload.33rc",
filename: "netbpfload.33rc",
installable: false,
}
diff --git a/netbpfload/NetBpfLoad.cpp b/netbpfload/NetBpfLoad.cpp
index 80df552..a247bda 100644
--- a/netbpfload/NetBpfLoad.cpp
+++ b/netbpfload/NetBpfLoad.cpp
@@ -224,11 +224,6 @@
return 0;
}
-static bool isGSI() {
- // From //system/gsid/libgsi.cpp IsGsiRunning()
- return !access("/metadata/gsi/dsu/booted", F_OK);
-}
-
static bool hasGSM() {
static string ph = base::GetProperty("gsm.current.phone-type", "");
static bool gsm = (ph != "");
@@ -254,7 +249,10 @@
}
static int doLoad(char** argv, char * const envp[]) {
- const int device_api_level = android_get_device_api_level();
+ const bool runningAsRoot = !getuid(); // true iff U QPR3 or V+
+ const bool unreleased = (base::GetProperty("ro.build.version.codename", "") != "REL");
+
+ const int device_api_level = android_get_device_api_level() + (int)unreleased;
const bool isAtLeastT = (device_api_level >= __ANDROID_API_T__);
const bool isAtLeastU = (device_api_level >= __ANDROID_API_U__);
const bool isAtLeastV = (device_api_level >= __ANDROID_API_V__);
@@ -264,9 +262,16 @@
// first in U QPR2 beta~2
const bool has_platform_netbpfload_rc = exists("/system/etc/init/netbpfload.rc");
- ALOGI("NetBpfLoad (%s) api:%d/%d kver:%07x (%s) rc:%d%d",
- argv[0], android_get_application_target_sdk_version(), device_api_level,
- kernelVersion(), describeArch(),
+ // Version of Network BpfLoader depends on the Android OS version
+ unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
+ if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
+ if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
+ if (runningAsRoot) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_U_QPR3_VERSION
+ if (isAtLeastV) ++bpfloader_ver; // [46] BPFLOADER_MAINLINE_V_VERSION
+
+ ALOGI("NetBpfLoad v0.%u (%s) api:%d/%d kver:%07x (%s) uid:%d rc:%d%d",
+ bpfloader_ver, argv[0], android_get_device_api_level(), device_api_level,
+ kernelVersion(), describeArch(), getuid(),
has_platform_bpfloader_rc, has_platform_netbpfload_rc);
if (!has_platform_bpfloader_rc && !has_platform_netbpfload_rc) {
@@ -341,7 +346,7 @@
#undef REQUIRE
- if (bad && !isGSI()) {
+ if (bad) {
ALOGE("Unsupported kernel version (%07x).", kernelVersion());
}
}
@@ -381,7 +386,9 @@
return 1;
}
- if (isAtLeastV) {
+ if (runningAsRoot) {
+ // Note: writing this proc file requires being root (always the case on V+)
+
// Linux 5.16-rc1 changed the default to 2 (disabled but changeable),
// but we need 0 (enabled)
// (this writeFile is known to fail on at least 4.19, but always defaults to 0 on
@@ -391,6 +398,11 @@
}
if (isAtLeastU) {
+ // Note: writing these proc files requires CAP_NET_ADMIN
+ // and sepolicy which is only present on U+,
+ // on Android T and earlier versions they're written from the 'load_bpf_programs'
+ // trigger (ie. by init itself) instead.
+
// Enable the eBPF JIT -- but do note that on 64-bit kernels it is likely
// already force enabled by the kernel config option BPF_JIT_ALWAYS_ON.
// (Note: this (open) will fail with ENOENT 'No such file or directory' if
@@ -420,12 +432,6 @@
// Thus we need to manually create the /sys/fs/bpf/loader subdirectory.
if (createSysFsBpfSubDir("loader")) return 1;
- // Version of Network BpfLoader depends on the Android OS version
- unsigned int bpfloader_ver = 42u; // [42] BPFLOADER_MAINLINE_VERSION
- if (isAtLeastT) ++bpfloader_ver; // [43] BPFLOADER_MAINLINE_T_VERSION
- if (isAtLeastU) ++bpfloader_ver; // [44] BPFLOADER_MAINLINE_U_VERSION
- if (isAtLeastV) ++bpfloader_ver; // [45] BPFLOADER_MAINLINE_V_VERSION
-
// Load all ELF objects, create programs and maps, and pin them
for (const auto& location : locations) {
if (loadAllElfObjects(bpfloader_ver, location) != 0) {
@@ -448,17 +454,25 @@
return 1;
}
- if (isAtLeastV) {
- ALOGI("done, transferring control to platform bpfloader.");
+ // leave a flag that we're done
+ if (createSysFsBpfSubDir("netd_shared/mainline_done")) return 1;
- const char * args[] = { platformBpfLoader, NULL, };
- execve(args[0], (char**)args, envp);
- ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
- return 1;
+ // platform bpfloader will only succeed when run as root
+ if (!runningAsRoot) {
+ // unreachable on U QPR3+ which always runs netbpfload as root
+
+ ALOGI("mainline done, no need to transfer control to platform bpf loader.");
+ return 0;
}
- ALOGI("mainline done!");
- return 0;
+ // unreachable before U QPR3
+ ALOGI("done, transferring control to platform bpfloader.");
+
+ // platform BpfLoader *needs* to run as root
+ const char * args[] = { platformBpfLoader, NULL, };
+ execve(args[0], (char**)args, envp);
+ ALOGE("FATAL: execve('%s'): %d[%s]", platformBpfLoader, errno, strerror(errno));
+ return 1;
}
} // namespace bpf
diff --git a/netbpfload/netbpfload.33rc b/netbpfload/netbpfload.33rc
new file mode 100644
index 0000000..05f566e
--- /dev/null
+++ b/netbpfload/netbpfload.33rc
@@ -0,0 +1,9 @@
+service mdnsd_netbpfload /apex/com.android.tethering/bin/netbpfload
+ capabilities CHOWN SYS_ADMIN NET_ADMIN
+ group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
+ user system
+ file /dev/kmsg w
+ rlimit memlock 1073741824 1073741824
+ oneshot
+ reboot_on_failure reboot,bpfloader-failed
+ override
diff --git a/netbpfload/netbpfload.mainline.rc b/netbpfload/netbpfload.mainline.rc
deleted file mode 100644
index d38a503..0000000
--- a/netbpfload/netbpfload.mainline.rc
+++ /dev/null
@@ -1,17 +0,0 @@
-service mdnsd_loadbpf /system/bin/bpfloader
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw system
- user root
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
-
-service bpfloader /apex/com.android.tethering/bin/netbpfload
- capabilities CHOWN SYS_ADMIN NET_ADMIN
- group system root graphics network_stack net_admin net_bw_acct net_bw_stats net_raw
- user system
- file /dev/kmsg w
- rlimit memlock 1073741824 1073741824
- oneshot
- reboot_on_failure reboot,bpfloader-failed
- override
diff --git a/netd/BpfHandler.cpp b/netd/BpfHandler.cpp
index aa84089..6cdf97b 100644
--- a/netd/BpfHandler.cpp
+++ b/netd/BpfHandler.cpp
@@ -34,6 +34,7 @@
namespace net {
using base::unique_fd;
+using base::WaitForProperty;
using bpf::getSocketCookie;
using bpf::retrieveProgram;
using netdutils::Status;
@@ -140,39 +141,47 @@
BpfHandler::BpfHandler(uint32_t perUidLimit, uint32_t totalLimit)
: mPerUidStatsEntriesLimit(perUidLimit), mTotalUidStatsEntriesLimit(totalLimit) {}
+static bool mainlineNetBpfLoadDone() {
+ return !access("/sys/fs/bpf/netd_shared/mainline_done", F_OK);
+}
+
// copied with minor changes from waitForProgsLoaded()
// p/m/C's staticlibs/native/bpf_headers/include/bpf/WaitForProgsLoaded.h
static inline void waitForNetProgsLoaded() {
// infinite loop until success with 5/10/20/40/60/60/60... delay
for (int delay = 5;; delay *= 2) {
if (delay > 60) delay = 60;
- if (base::WaitForProperty("init.svc.bpfloader", "stopped", std::chrono::seconds(delay))
- && !access("/sys/fs/bpf/netd_shared", F_OK))
+ if (WaitForProperty("init.svc.mdnsd_netbpfload", "stopped", std::chrono::seconds(delay))
+ && mainlineNetBpfLoadDone())
return;
- ALOGW("Waited %ds for init.svc.bpfloader=stopped, still waiting...", delay);
+ ALOGW("Waited %ds for init.svc.mdnsd_netbpfload=stopped, still waiting...", delay);
}
}
Status BpfHandler::init(const char* cg2_path) {
+ // Note: netd *can* be restarted, so this might get called a second time after boot is complete
+ // at which point we don't need to (and shouldn't) wait for (more importantly start) loading bpf
+
if (base::GetProperty("bpf.progs_loaded", "") != "1") {
- // Make sure BPF programs are loaded before doing anything
- ALOGI("Waiting for BPF programs");
-
- // TODO: use !modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() < __ANDROID_API_V__) {
- waitForNetProgsLoaded();
- ALOGI("Networking BPF programs are loaded");
-
- if (!base::SetProperty("ctl.start", "mdnsd_loadbpf")) {
- ALOGE("Failed to set property ctl.start=mdnsd_loadbpf, see dmesg for reason.");
- abort();
- }
-
- ALOGI("Waiting for remaining BPF programs");
- }
-
+ // AOSP platform netd & mainline don't need this (at least prior to U QPR3),
+ // but there could be platform provided (xt_)bpf programs that oem/vendor
+ // modified netd (which calls us during init) depends on...
+ ALOGI("Waiting for platform BPF programs");
android::bpf::waitForProgsLoaded();
}
+
+ if (false && !mainlineNetBpfLoadDone()) {
+ // we're on < U QPR3 & it's the first time netd is starting up (unless crashlooping)
+ if (!base::SetProperty("ctl.start", "mdnsd_netbpfload")) {
+ ALOGE("Failed to set property ctl.start=mdnsd_netbpfload, see dmesg for reason.");
+ abort();
+ }
+
+ ALOGI("Waiting for Networking BPF programs");
+ waitForNetProgsLoaded();
+ ALOGI("Networking BPF programs are loaded");
+ }
+
ALOGI("BPF programs are loaded");
RETURN_IF_NOT_OK(initPrograms(cg2_path));
diff --git a/service-t/src/com/android/server/NsdService.java b/service-t/src/com/android/server/NsdService.java
index f8b0d53..64624ae 100644
--- a/service-t/src/com/android/server/NsdService.java
+++ b/service-t/src/com/android/server/NsdService.java
@@ -1923,13 +1923,13 @@
mContext, MdnsFeatureFlags.NSD_FORCE_DISABLE_MDNS_OFFLOAD))
.setIncludeInetAddressRecordsInProbing(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.INCLUDE_INET_ADDRESS_RECORDS_IN_PROBING))
- .setIsExpiredServicesRemovalEnabled(mDeps.isFeatureEnabled(
+ .setIsExpiredServicesRemovalEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_EXPIRED_SERVICES_REMOVAL))
.setIsLabelCountLimitEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_LIMIT_LABEL_COUNT))
- .setIsKnownAnswerSuppressionEnabled(mDeps.isFeatureEnabled(
+ .setIsKnownAnswerSuppressionEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_KNOWN_ANSWER_SUPPRESSION))
- .setIsUnicastReplyEnabled(mDeps.isFeatureEnabled(
+ .setIsUnicastReplyEnabled(mDeps.isTetheringFeatureNotChickenedOut(
mContext, MdnsFeatureFlags.NSD_UNICAST_REPLY_ENABLED))
.setIsAggressiveQueryModeEnabled(mDeps.isFeatureEnabled(
mContext, MdnsFeatureFlags.NSD_AGGRESSIVE_QUERY_MODE))
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 f4a08ba..c264f25 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsFeatureFlags.java
@@ -189,10 +189,10 @@
public Builder() {
mIsMdnsOffloadFeatureEnabled = false;
mIncludeInetAddressRecordsInProbing = false;
- mIsExpiredServicesRemovalEnabled = false;
+ mIsExpiredServicesRemovalEnabled = true; // Default enabled.
mIsLabelCountLimitEnabled = true; // Default enabled.
- mIsKnownAnswerSuppressionEnabled = false;
- mIsUnicastReplyEnabled = true;
+ mIsKnownAnswerSuppressionEnabled = true; // Default enabled.
+ mIsUnicastReplyEnabled = true; // Default enabled.
mIsAggressiveQueryModeEnabled = false;
mIsQueryWithKnownAnswerEnabled = false;
mOverrideProvider = null;
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index c07d050..18a274a 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -114,12 +114,7 @@
V("/sys/fs/bpf", S_IFDIR|S_ISVTX|0777, ROOT, ROOT, "fs_bpf", DIR);
- // TODO: use modules::sdklevel::IsAtLeastV() once api finalized
- if (android_get_device_api_level() >= __ANDROID_API_V__) {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
- } else {
- V("/sys/fs/bpf/net_shared", S_IFDIR|01777, SYSTEM, SYSTEM, "fs_bpf_net_shared", DIR);
- }
+ V("/sys/fs/bpf/net_shared", S_IFDIR|01777, ROOT, ROOT, "fs_bpf_net_shared", DIR);
// pre-U we do not have selinux privs to getattr on bpf maps/progs
// so while the below *should* be as listed, we have no way to actually verify
diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
index 7707122..fd41ee6 100644
--- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java
@@ -170,9 +170,11 @@
&& !TextUtils.isEmpty(nai.linkProperties.getCaptivePortalData()
.getVenueFriendlyName())) {
name = nai.linkProperties.getCaptivePortalData().getVenueFriendlyName();
+ } else if (!TextUtils.isEmpty(extraInfo)) {
+ name = extraInfo;
} else {
- name = TextUtils.isEmpty(extraInfo)
- ? WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid()) : extraInfo;
+ final String ssid = WifiInfo.sanitizeSsid(nai.networkCapabilities.getSsid());
+ name = ssid == null ? "" : ssid;
}
// Only notify for Internet-capable networks.
if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_INTERNET)) return;
diff --git a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
index dc7925e..6864bad 100644
--- a/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
+++ b/staticlibs/native/bpf_headers/include/bpf/bpf_helpers.h
@@ -39,11 +39,12 @@
// Android U / 14 (api level 34) - various new program types added
#define BPFLOADER_U_VERSION 38u
-// Android V / 15 (api level 35) - platform only
+// Android U QPR2 / 14 (api level 34) - platform only
// (note: the platform bpfloader in V isn't really versioned at all,
// as there is no need as it can only load objects compiled at the
// same time as itself and the rest of the platform)
-#define BPFLOADER_PLATFORM_VERSION 41u
+#define BPFLOADER_U_QPR2_VERSION 41u
+#define BPFLOADER_PLATFORM_VERSION BPFLOADER_U_QPR2_VERSION
// Android Mainline - this bpfloader should eventually go back to T (or even S)
// Note: this value (and the following +1u's) are hardcoded in NetBpfLoad.cpp
@@ -55,8 +56,11 @@
// Android Mainline BpfLoader when running on Android U
#define BPFLOADER_MAINLINE_U_VERSION (BPFLOADER_MAINLINE_T_VERSION + 1u)
+// Android Mainline BpfLoader when running on Android U QPR3
+#define BPFLOADER_MAINLINE_U_QPR3_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+
// Android Mainline BpfLoader when running on Android V
-#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_VERSION + 1u)
+#define BPFLOADER_MAINLINE_V_VERSION (BPFLOADER_MAINLINE_U_QPR3_VERSION + 1u)
/* For mainline module use, you can #define BPFLOADER_{MIN/MAX}_VER
* before #include "bpf_helpers.h" to change which bpfloaders will
diff --git a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
index df6067d..e634f0e 100644
--- a/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
+++ b/staticlibs/testutils/app/connectivitychecker/src/com/android/testutils/connectivitypreparer/ConnectivityCheckTest.kt
@@ -34,17 +34,13 @@
private val connectUtil by lazy { ConnectUtil(context) }
@Test
- fun testCheckConnectivity() {
- checkWifiSetup()
- checkTelephonySetup()
- }
-
- private fun checkWifiSetup() {
+ fun testCheckWifiSetup() {
if (!pm.hasSystemFeature(FEATURE_WIFI)) return
connectUtil.ensureWifiValidated()
}
- private fun checkTelephonySetup() {
+ @Test
+ fun testCheckTelephonySetup() {
if (!pm.hasSystemFeature(FEATURE_TELEPHONY)) return
val tm = context.getSystemService(TelephonyManager::class.java)
?: fail("Could not get telephony service")
@@ -52,7 +48,7 @@
val commonError = "Check the test bench. To run the tests anyway for quick & dirty local " +
"testing, you can use atest X -- " +
"--test-arg com.android.testutils.ConnectivityTestTargetPreparer" +
- ":ignore-connectivity-check:true"
+ ":ignore-mobile-data-check:true"
// Do not use assertEquals: it outputs "expected X, was Y", which looks like a test failure
if (tm.simState == TelephonyManager.SIM_STATE_ABSENT) {
fail("The device has no SIM card inserted. $commonError")
diff --git a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
index 6d03042..435fdd8 100644
--- a/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
+++ b/staticlibs/testutils/host/com/android/testutils/ConnectivityTestTargetPreparer.kt
@@ -28,9 +28,11 @@
private const val CONNECTIVITY_CHECKER_APK = "ConnectivityTestPreparer.apk"
private const val CONNECTIVITY_PKG_NAME = "com.android.testutils.connectivitypreparer"
private const val CONNECTIVITY_CHECK_CLASS = "$CONNECTIVITY_PKG_NAME.ConnectivityCheckTest"
+
// As per the <instrumentation> defined in the checker manifest
private const val CONNECTIVITY_CHECK_RUNNER_NAME = "androidx.test.runner.AndroidJUnitRunner"
-private const val IGNORE_CONN_CHECK_OPTION = "ignore-connectivity-check"
+private const val IGNORE_WIFI_CHECK = "ignore-wifi-check"
+private const val IGNORE_MOBILE_DATA_CHECK = "ignore-mobile-data-check"
// The default updater package names, which might be updating packages while the CTS
// are running
@@ -41,14 +43,23 @@
*
* For quick and dirty local testing, the connectivity check can be disabled by running tests with
* "atest -- \
- * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-connectivity-check:true".
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-mobile-data-check:true". \
+ * --test-arg com.android.testutils.ConnectivityTestTargetPreparer:ignore-wifi-check:true".
*/
open class ConnectivityTestTargetPreparer : BaseTargetPreparer() {
private val installer = SuiteApkInstaller()
- @Option(name = IGNORE_CONN_CHECK_OPTION,
- description = "Disables the check for mobile data and wifi")
- private var ignoreConnectivityCheck = false
+ @Option(
+ name = IGNORE_WIFI_CHECK,
+ description = "Disables the check for wifi"
+ )
+ private var ignoreWifiCheck = false
+ @Option(
+ name = IGNORE_MOBILE_DATA_CHECK,
+ description = "Disables the check for mobile data"
+ )
+ private var ignoreMobileDataCheck = false
+
// The default value is never used, but false is a reasonable default
private var originalTestChainEnabled = false
private val originalUpdaterPkgsStatus = HashMap<String, Boolean>()
@@ -58,44 +69,62 @@
disableGmsUpdate(testInfo)
originalTestChainEnabled = getTestChainEnabled(testInfo)
originalUpdaterPkgsStatus.putAll(getUpdaterPkgsStatus(testInfo))
- setUpdaterNetworkingEnabled(testInfo, enableChain = true,
- enablePkgs = UPDATER_PKGS.associateWith { false })
- runPreparerApk(testInfo)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = true,
+ enablePkgs = UPDATER_PKGS.associateWith { false }
+ )
+ runConnectivityCheckApk(testInfo)
refreshTime(testInfo)
}
- private fun runPreparerApk(testInfo: TestInformation) {
+ private fun runConnectivityCheckApk(testInfo: TestInformation) {
installer.setCleanApk(true)
installer.addTestFileName(CONNECTIVITY_CHECKER_APK)
installer.setShouldGrantPermission(true)
installer.setUp(testInfo)
+ val testMethods = mutableListOf<String>()
+ if (!ignoreWifiCheck) {
+ testMethods.add("testCheckWifiSetup")
+ }
+ if (!ignoreMobileDataCheck) {
+ testMethods.add("testCheckTelephonySetup")
+ }
+
+ testMethods.forEach {
+ runTestMethod(testInfo, it)
+ }
+ }
+
+ private fun runTestMethod(testInfo: TestInformation, method: String) {
val runner = DefaultRemoteAndroidTestRunner(
- CONNECTIVITY_PKG_NAME,
- CONNECTIVITY_CHECK_RUNNER_NAME,
- testInfo.device.iDevice)
+ CONNECTIVITY_PKG_NAME,
+ CONNECTIVITY_CHECK_RUNNER_NAME,
+ testInfo.device.iDevice
+ )
runner.runOptions = "--no-hidden-api-checks"
+ runner.setMethodName(CONNECTIVITY_CHECK_CLASS, method)
val receiver = CollectingTestListener()
if (!testInfo.device.runInstrumentationTests(runner, receiver)) {
- throw TargetSetupError("Device state check failed to complete",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device state check failed to complete",
+ testInfo.device.deviceDescriptor
+ )
}
val runResult = receiver.currentRunResults
if (runResult.isRunFailure) {
- throw TargetSetupError("Failed to check device state before the test: " +
- runResult.runFailureMessage, testInfo.device.deviceDescriptor)
- }
-
- val ignoredTestClasses = mutableSetOf<String>()
- if (ignoreConnectivityCheck) {
- ignoredTestClasses.add(CONNECTIVITY_CHECK_CLASS)
+ throw TargetSetupError(
+ "Failed to check device state before the test: " +
+ runResult.runFailureMessage,
+ testInfo.device.deviceDescriptor
+ )
}
val errorMsg = runResult.testResults.mapNotNull { (testDescription, testResult) ->
- if (TestResult.TestStatus.FAILURE != testResult.status ||
- ignoredTestClasses.contains(testDescription.className)) {
+ if (TestResult.TestStatus.FAILURE != testResult.status) {
null
} else {
"$testDescription: ${testResult.stackTrace}"
@@ -103,21 +132,27 @@
}.joinToString("\n")
if (errorMsg.isBlank()) return
- throw TargetSetupError("Device setup checks failed. Check the test bench: \n$errorMsg",
- testInfo.device.deviceDescriptor)
+ throw TargetSetupError(
+ "Device setup checks failed. Check the test bench: \n$errorMsg",
+ testInfo.device.deviceDescriptor
+ )
}
private fun disableGmsUpdate(testInfo: TestInformation) {
// This will be a no-op on devices without root (su) or not using gservices, but that's OK.
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "-e finsky.play_services_auto_update_enabled false")
+ "-e finsky.play_services_auto_update_enabled false"
+ )
}
private fun clearGmsUpdateOverride(testInfo: TestInformation) {
- testInfo.exec("su 0 am broadcast " +
+ testInfo.exec(
+ "su 0 am broadcast " +
"-a com.google.gservices.intent.action.GSERVICES_OVERRIDE " +
- "--esn finsky.play_services_auto_update_enabled")
+ "--esn finsky.play_services_auto_update_enabled"
+ )
}
private fun setUpdaterNetworkingEnabled(
@@ -137,10 +172,10 @@
testInfo.exec("cmd connectivity get-chain3-enabled").contains("chain:enabled")
private fun getUpdaterPkgsStatus(testInfo: TestInformation) =
- UPDATER_PKGS.associateWith { pkg ->
- !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
- .contains(":deny")
- }
+ UPDATER_PKGS.associateWith { pkg ->
+ !testInfo.exec("cmd connectivity get-package-networking-enabled $pkg")
+ .contains(":deny")
+ }
private fun refreshTime(testInfo: TestInformation,) {
// Forces a synchronous time refresh using the network. Time is fetched synchronously but
@@ -153,9 +188,11 @@
override fun tearDown(testInfo: TestInformation, e: Throwable?) {
if (isTearDownDisabled) return
installer.tearDown(testInfo, e)
- setUpdaterNetworkingEnabled(testInfo,
- enableChain = originalTestChainEnabled,
- enablePkgs = originalUpdaterPkgsStatus)
+ setUpdaterNetworkingEnabled(
+ testInfo,
+ enableChain = originalTestChainEnabled,
+ enablePkgs = originalUpdaterPkgsStatus
+ )
clearGmsUpdateOverride(testInfo)
}
}
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 38f26d8..077c3ef 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -24,6 +24,11 @@
<option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
<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="target" value="device" />
+ <option name="config-filename" value="{MODULE}" />
+ <option name="version" value="1.0" />
+ </target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="{MODULE}.apk" />
@@ -38,6 +43,7 @@
<option name="runtime-hint" value="9m4s" />
<option name="hidden-api-checks" value="false" />
<option name="isolated-storage" value="false" />
+ <option name="instrumentation-arg" key="test-module-name" value="{MODULE}" />
<!-- Test filter that allows test APKs to select which tests they want to run by annotating
those tests with an annotation matching the name of the APK.
diff --git a/tests/cts/net/DynamicConfig.xml b/tests/cts/net/DynamicConfig.xml
new file mode 100644
index 0000000..af019c2
--- /dev/null
+++ b/tests/cts/net/DynamicConfig.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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.
+ -->
+
+<dynamicConfig>
+ <entry key="remote_config_required">
+ <value>false</value>
+ </entry>
+ <entry key="IP_ADDRESS_ECHO_URL">
+ <value>https://google-ipv6test.appspot.com/ip.js?fmt=text</value>
+ </entry>
+</dynamicConfig>
diff --git a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
index 0b6637d..086f0e7 100644
--- a/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
+++ b/tests/cts/net/src/android/net/cts/ApfIntegrationTest.kt
@@ -361,7 +361,8 @@
@SkipPresubmit(reason = "This test takes longer than 1 minute, do not run it on presubmit.")
// APF integration is mostly broken before V, only run the full read / write test on V+.
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
- @Test
+ // Increase timeout for test to 15 minutes to accommodate device with large APF RAM.
+ @Test(timeout = 15 * 60 * 1000)
fun testReadWriteProgram() {
assumeApfVersionSupportAtLeast(4)
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index 0e926cc..60869b6 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -174,6 +174,7 @@
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.MessageQueue;
@@ -192,10 +193,11 @@
import android.util.Log;
import android.util.Range;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.CollectionUtils;
@@ -249,6 +251,7 @@
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
@@ -335,6 +338,11 @@
private static final String TEST_HTTPS_URL_PATH = "/https_path";
private static final String TEST_HTTP_URL_PATH = "/http_path";
private static final String LOCALHOST_HOSTNAME = "localhost";
+ private static final String TEST_MODULE_NAME_OPTION = "test-module-name";
+ private static final String IP_ADDRESS_ECHO_URL_KEY = "IP_ADDRESS_ECHO_URL";
+ private static final List<String> ALLOWED_IP_ADDRESS_ECHO_URLS = Arrays.asList(
+ "https://google-ipv6test.appspot.com/ip.js?fmt=text",
+ "https://ipv6test.googleapis-cn.com/ip.js?fmt=text");
// Re-connecting to the AP, obtaining an IP address, revalidating can take a long time
private static final long WIFI_CONNECT_TIMEOUT_MS = 60_000L;
@@ -855,7 +863,7 @@
* Tests that connections can be opened on WiFi and cellphone networks,
* and that they are made from different IP addresses.
*/
- @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+ @AppModeFull(reason = "Cannot get WifiManager or access the SD card in instant app mode")
@Test
@RequiresDevice // Virtual devices use a single internet connection for all networks
public void testOpenConnection() throws Exception {
@@ -865,7 +873,8 @@
Network wifiNetwork = mCtsNetUtils.ensureWifiConnected();
Network cellNetwork = networkCallbackRule.requestCell();
// This server returns the requestor's IP address as the response body.
- URL url = new URL("http://google-ipv6test.appspot.com/ip.js?fmt=text");
+ String ipAddressEchoUrl = getIpAddressEchoUrlFromConfig();
+ URL url = new URL(ipAddressEchoUrl);
String wifiAddressString = httpGet(wifiNetwork, url);
String cellAddressString = httpGet(cellNetwork, url);
@@ -882,6 +891,19 @@
}
/**
+ * Gets IP address echo url from dynamic config.
+ */
+ private static String getIpAddressEchoUrlFromConfig() throws Exception {
+ Bundle instrumentationArgs = InstrumentationRegistry.getArguments();
+ String testModuleName = instrumentationArgs.getString(TEST_MODULE_NAME_OPTION);
+ // Get the DynamicConfig.xml contents and extract the ipv6 test URL.
+ DynamicConfigDeviceSide dynamicConfig = new DynamicConfigDeviceSide(testModuleName);
+ String ipAddressEchoUrl = dynamicConfig.getValue(IP_ADDRESS_ECHO_URL_KEY);
+ assertContains(ALLOWED_IP_ADDRESS_ECHO_URLS, ipAddressEchoUrl);
+ return ipAddressEchoUrl;
+ }
+
+ /**
* Performs a HTTP GET to the specified URL on the specified Network, and returns
* the response body decoded as UTF-8.
*/
diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
index 7121ed4..727db58 100644
--- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java
@@ -113,6 +113,7 @@
private static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities();
private static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities();
+ private static final NetworkCapabilities BT_CAPABILITIES = new NetworkCapabilities();
static {
CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
@@ -128,6 +129,9 @@
VPN_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_VPN);
VPN_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
+
+ BT_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ BT_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
}
/**
@@ -159,7 +163,9 @@
@Mock NetworkAgentInfo mWifiNai;
@Mock NetworkAgentInfo mCellNai;
@Mock NetworkAgentInfo mVpnNai;
+ @Mock NetworkAgentInfo mBluetoothNai;
@Mock NetworkInfo mNetworkInfo;
+ @Mock NetworkInfo mEmptyNetworkInfo;
ArgumentCaptor<Notification> mCaptor;
NetworkNotificationManager mManager;
@@ -174,6 +180,8 @@
mCellNai.networkInfo = mNetworkInfo;
mVpnNai.networkCapabilities = VPN_CAPABILITIES;
mVpnNai.networkInfo = mNetworkInfo;
+ mBluetoothNai.networkCapabilities = BT_CAPABILITIES;
+ mBluetoothNai.networkInfo = mEmptyNetworkInfo;
mDisplayMetrics.density = 2.275f;
doReturn(true).when(mVpnNai).isVPN();
doReturn(mResources).when(mCtx).getResources();
@@ -542,10 +550,11 @@
R.string.wifi_no_internet_detailed);
}
- private void runTelephonySignInNotificationTest(String testTitle, String testContents) {
+ private void runSignInNotificationTest(NetworkAgentInfo nai, String testTitle,
+ String testContents) {
final int id = 101;
final String tag = NetworkNotificationManager.tagFor(id);
- mManager.showNotification(id, SIGN_IN, mCellNai, null, null, false);
+ mManager.showNotification(id, SIGN_IN, nai, null, null, false);
final ArgumentCaptor<Notification> noteCaptor = ArgumentCaptor.forClass(Notification.class);
verify(mNotificationManager).notify(eq(tag), eq(SIGN_IN.eventId), noteCaptor.capture());
@@ -565,7 +574,7 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed, TEST_OPERATOR_NAME);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
}
@Test
@@ -579,6 +588,21 @@
doReturn(testContents).when(mResources).getString(
R.string.mobile_network_available_no_internet_detailed_unknown_carrier);
- runTelephonySignInNotificationTest(testTitle, testContents);
+ runSignInNotificationTest(mCellNai, testTitle, testContents);
+ }
+
+ @Test
+ public void testBluetoothSignInNotification_EmptyNotificationContents() {
+ final String testTitle = "Test title";
+ final String testContents = "Test contents";
+ doReturn(testTitle).when(mResources).getString(
+ R.string.network_available_sign_in, 0);
+ doReturn(testContents).when(mResources).getString(
+ eq(R.string.network_available_sign_in_detailed), any());
+
+ runSignInNotificationTest(mBluetoothNai, testTitle, testContents);
+ // The details should be queried with an empty string argument. In practice the notification
+ // contents may just be an empty string, since the default translation just outputs the arg.
+ verify(mResources).getString(eq(R.string.network_available_sign_in_detailed), eq(""));
}
}