Merge remote-tracking branch 'goog/sc-dev-plus-aosp' into merge-sc-dev-plus-aosp-then-rvc-qpr-dev-plus-aosp
This CL merges sc-dev-plus-aosp into mainline-prod. It picks up
changes that:
- Were merged into sc-dev and automerged into sc-dev-plus-aosp
- Were merged into aosp master while sc-dev-plus-aosp was on the
path between AOSP and master.
It does not pick up changes that were merged into aosp before
sc-dev-plus-aosp was on the merge path from AOSP to master. One
such very simple CL is aosp/1554765.
There were no merge conflicts.
Current diffstat with aosp/master is:
161 files changed, 2923 insertions(+), 4005 deletions(-)
This includes 1800 lines of translations added in 72 files like
Tethering/res/values-*/strings.xml
Bug: 167645754
Test: no merge conflicts
Test: didn't even try to build
Ignore-AOSP-First: this is a merge from AOSP
Change-Id: Ie82ff0d3fcb82f0d66534b114af9f240f5574bfb
Merged-In: Ib0ac49609e444a53a6fee4575f5078e15f364eef
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ccff052
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+# Eclipse project
+**/.classpath
+**/.project
+
+# IntelliJ project
+**/.idea
+**/*.iml
+**/*.ipr
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..0e1e65d
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,6 @@
+codewiz@google.com
+jchalard@google.com
+junyulai@google.com
+lorenzo@google.com
+reminv@google.com
+satk@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
new file mode 100644
index 0000000..ebc1264
--- /dev/null
+++ b/PREUPLOAD.cfg
@@ -0,0 +1,4 @@
+[Hook Scripts]
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+
+ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
diff --git a/TEST_MAPPING b/TEST_MAPPING
new file mode 100644
index 0000000..5128519
--- /dev/null
+++ b/TEST_MAPPING
@@ -0,0 +1,33 @@
+{
+ // Run in addition to mainline-presubmit as mainline-presubmit is not
+ // supported in every branch.
+ "presubmit": [
+ {
+ "name": "CtsNetTestCasesLatestSdk",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
+ }
+ ],
+ "mainline-presubmit": [
+ {
+ // TODO: add back the tethering modules when updatable in this branch
+ "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex]",
+ "options": [
+ {
+ "exclude-annotation": "com.android.testutils.SkipPresubmit"
+ }
+ ]
+ }
+ ],
+ // Tests on physical devices with SIM cards: postsubmit only for capacity constraints
+ "mainline-postsubmit": [
+ {
+ // TODO: add back the tethering module when updatable in this branch
+ "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex]",
+ "keywords": ["sim"]
+ }
+ ]
+}
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 0957526..7427481 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -19,6 +19,7 @@
sdk_version: "module_current",
min_sdk_version: "30",
srcs: [
+ "apishim/**/*.java",
"src/**/*.java",
":framework-tethering-shared-srcs",
":tethering-module-utils-srcs",
@@ -26,13 +27,15 @@
],
static_libs: [
"androidx.annotation_annotation",
- "netd_aidl_interface-java",
+ "modules-utils-build",
"netlink-client",
- "networkstack-aidl-interfaces-java",
+ // TODO: use networkstack-client instead of just including the AIDL interface
+ "networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.config-V1.0-java",
"android.hardware.tetheroffload.control-V1.0-java",
"net-utils-framework-common",
"net-utils-device-common",
+ "netd-client",
],
libs: [
"framework-statsd.stubs.module_lib",
@@ -59,8 +62,9 @@
"com.android.tethering",
],
min_sdk_version: "30",
+ header_libs: ["bpf_syscall_wrappers"],
srcs: [
- "jni/android_net_util_TetheringUtils.cpp",
+ "jni/*.cpp",
],
shared_libs: [
"liblog",
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index a6ea99e..c99121c 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -16,9 +16,18 @@
apex {
name: "com.android.tethering",
- updatable: true,
- min_sdk_version: "30",
- java_libs: ["framework-tethering"],
+ // TODO: make updatable again once this contains only updatable artifacts (in particular, this
+ // cannot build as updatable unless service-connectivity builds against stable API).
+ // updatable: true,
+ // min_sdk_version: "30",
+ java_libs: [
+ "framework-tethering",
+ "service-connectivity",
+ ],
+ jni_libs: [
+ "libservice-connectivity",
+ ],
+ bpfs: ["offload.o"],
apps: ["Tethering"],
manifest: "manifest.json",
key: "com.android.tethering.key",
diff --git a/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
new file mode 100644
index 0000000..5cf0384
--- /dev/null
+++ b/Tethering/apishim/30/com/android/networkstack/tethering/apishim/api30/BpfCoordinatorShimImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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.networkstack.tethering.apishim.api30;
+
+import android.net.INetd;
+import android.net.util.SharedLog;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import androidx.annotation.NonNull;
+
+import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+
+/**
+ * Bpf coordinator class for API shims.
+ */
+public class BpfCoordinatorShimImpl
+ extends com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim {
+ private static final String TAG = "api30.BpfCoordinatorShimImpl";
+
+ @NonNull
+ private final SharedLog mLog;
+ @NonNull
+ private final INetd mNetd;
+
+ public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
+ mLog = deps.getSharedLog().forSubComponent(TAG);
+ mNetd = deps.getNetd();
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return true;
+ };
+
+ @Override
+ public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) {
+ try {
+ mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Could not add IPv6 forwarding rule: ", e);
+ return false;
+ }
+
+ return true;
+ };
+
+ @Override
+ public String toString() {
+ return "Netd used";
+ }
+}
diff --git a/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
new file mode 100644
index 0000000..03616ca
--- /dev/null
+++ b/Tethering/apishim/31/com/android/networkstack/tethering/apishim/api31/BpfCoordinatorShimImpl.java
@@ -0,0 +1,79 @@
+/*
+ * 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.networkstack.tethering.apishim.api31;
+
+import android.net.util.SharedLog;
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfMap;
+import com.android.networkstack.tethering.TetherIngressKey;
+import com.android.networkstack.tethering.TetherIngressValue;
+
+/**
+ * Bpf coordinator class for API shims.
+ */
+public class BpfCoordinatorShimImpl
+ extends com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim {
+ private static final String TAG = "api31.BpfCoordinatorShimImpl";
+
+ @NonNull
+ private final SharedLog mLog;
+
+ // BPF map of ingress queueing discipline which pre-processes the packets by the IPv6
+ // forwarding rules.
+ @Nullable
+ private final BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
+
+ public BpfCoordinatorShimImpl(@NonNull final Dependencies deps) {
+ mLog = deps.getSharedLog().forSubComponent(TAG);
+ mBpfIngressMap = deps.getBpfIngressMap();
+ }
+
+ @Override
+ public boolean isInitialized() {
+ return mBpfIngressMap != null;
+ }
+
+ @Override
+ public boolean tetherOffloadRuleAdd(@NonNull final Ipv6ForwardingRule rule) {
+ if (!isInitialized()) return false;
+
+ final TetherIngressKey key = rule.makeTetherIngressKey();
+ final TetherIngressValue value = rule.makeTetherIngressValue();
+
+ try {
+ mBpfIngressMap.updateEntry(key, value);
+ } catch (ErrnoException e) {
+ mLog.e("Could not update entry: ", e);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "mBpfIngressMap{"
+ + (mBpfIngressMap != null ? "initialized" : "not initialized") + "} "
+ + "}";
+ }
+}
diff --git a/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
new file mode 100644
index 0000000..bcb644c
--- /dev/null
+++ b/Tethering/apishim/common/com/android/networkstack/tethering/apishim/common/BpfCoordinatorShim.java
@@ -0,0 +1,58 @@
+/*
+ * 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.networkstack.tethering.apishim.common;
+
+import androidx.annotation.NonNull;
+
+import com.android.networkstack.tethering.BpfCoordinator.Dependencies;
+import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+
+/**
+ * Bpf coordinator class for API shims.
+ */
+public abstract class BpfCoordinatorShim {
+ /**
+ * Get BpfCoordinatorShim object by OS build version.
+ */
+ @NonNull
+ public static BpfCoordinatorShim getBpfCoordinatorShim(@NonNull final Dependencies deps) {
+ if (deps.isAtLeastS()) {
+ return new com.android.networkstack.tethering.apishim.api31.BpfCoordinatorShimImpl(
+ deps);
+ } else {
+ return new com.android.networkstack.tethering.apishim.api30.BpfCoordinatorShimImpl(
+ deps);
+ }
+ }
+
+ /**
+ * Return true if this class has been initialized, otherwise return false.
+ */
+ public abstract boolean isInitialized();
+
+ /**
+ * Adds a tethering offload rule to BPF map, or updates it if it already exists.
+ *
+ * Currently, only downstream /128 IPv6 entries are supported. An existing rule will be updated
+ * if the input interface and destination prefix match. Otherwise, a new rule will be created.
+ * Note that this can be only called on handler thread.
+ *
+ * @param rule The rule to add or update.
+ */
+ public abstract boolean tetherOffloadRuleAdd(@NonNull Ipv6ForwardingRule rule);
+}
+
diff --git a/Tethering/bpf_progs/Android.bp b/Tethering/bpf_progs/Android.bp
new file mode 100644
index 0000000..d54f861
--- /dev/null
+++ b/Tethering/bpf_progs/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+//
+
+//
+// bpf kernel programs
+//
+bpf {
+ name: "offload.o",
+ srcs: ["offload.c"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ include_dirs: [
+ // TODO: get rid of system/netd.
+ "system/netd/bpf_progs", // for bpf_net_helpers.h
+ "system/netd/libnetdbpf/include", // for bpf_shared.h
+ "system/netd/libnetdutils/include", // for UidConstants.h
+ ],
+}
diff --git a/Tethering/bpf_progs/offload.c b/Tethering/bpf_progs/offload.c
new file mode 100644
index 0000000..d8dc60d
--- /dev/null
+++ b/Tethering/bpf_progs/offload.c
@@ -0,0 +1,211 @@
+/*
+ * 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.
+ */
+
+#include <linux/if.h>
+#include <linux/ip.h>
+#include <linux/ipv6.h>
+#include <linux/pkt_cls.h>
+#include <linux/tcp.h>
+
+#include "bpf_helpers.h"
+#include "bpf_net_helpers.h"
+#include "netdbpf/bpf_shared.h"
+
+DEFINE_BPF_MAP_GRW(tether_ingress_map, HASH, TetherIngressKey, TetherIngressValue, 64,
+ AID_NETWORK_STACK)
+
+// Tethering stats, indexed by upstream interface.
+DEFINE_BPF_MAP_GRW(tether_stats_map, HASH, uint32_t, TetherStatsValue, 16, AID_NETWORK_STACK)
+
+// Tethering data limit, indexed by upstream interface.
+// (tethering allowed when stats[iif].rxBytes + stats[iif].txBytes < limit[iif])
+DEFINE_BPF_MAP_GRW(tether_limit_map, HASH, uint32_t, uint64_t, 16, AID_NETWORK_STACK)
+
+// Used only by TetheringPrivilegedTests, not by production code.
+DEFINE_BPF_MAP_GRW(tether_ingress_map_TEST, HASH, TetherIngressKey, TetherIngressValue, 16,
+ AID_NETWORK_STACK)
+
+static inline __always_inline int do_forward(struct __sk_buff* skb, bool is_ethernet) {
+ int l2_header_size = is_ethernet ? sizeof(struct ethhdr) : 0;
+ void* data = (void*)(long)skb->data;
+ const void* data_end = (void*)(long)skb->data_end;
+ struct ethhdr* eth = is_ethernet ? data : NULL; // used iff is_ethernet
+ struct ipv6hdr* ip6 = is_ethernet ? (void*)(eth + 1) : data;
+
+ // Must be meta-ethernet IPv6 frame
+ if (skb->protocol != htons(ETH_P_IPV6)) return TC_ACT_OK;
+
+ // Must have (ethernet and) ipv6 header
+ if (data + l2_header_size + sizeof(*ip6) > data_end) return TC_ACT_OK;
+
+ // Ethertype - if present - must be IPv6
+ if (is_ethernet && (eth->h_proto != htons(ETH_P_IPV6))) return TC_ACT_OK;
+
+ // IP version must be 6
+ if (ip6->version != 6) return TC_ACT_OK;
+
+ // Cannot decrement during forward if already zero or would be zero,
+ // Let the kernel's stack handle these cases and generate appropriate ICMP errors.
+ if (ip6->hop_limit <= 1) return TC_ACT_OK;
+
+ // Protect against forwarding packets sourced from ::1 or fe80::/64 or other weirdness.
+ __be32 src32 = ip6->saddr.s6_addr32[0];
+ if (src32 != htonl(0x0064ff9b) && // 64:ff9b:/32 incl. XLAT464 WKP
+ (src32 & htonl(0xe0000000)) != htonl(0x20000000)) // 2000::/3 Global Unicast
+ return TC_ACT_OK;
+
+ TetherIngressKey k = {
+ .iif = skb->ifindex,
+ .neigh6 = ip6->daddr,
+ };
+
+ TetherIngressValue* v = bpf_tether_ingress_map_lookup_elem(&k);
+
+ // If we don't find any offload information then simply let the core stack handle it...
+ if (!v) return TC_ACT_OK;
+
+ uint32_t stat_and_limit_k = skb->ifindex;
+
+ TetherStatsValue* stat_v = bpf_tether_stats_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have anywhere to put stats, then abort...
+ if (!stat_v) return TC_ACT_OK;
+
+ uint64_t* limit_v = bpf_tether_limit_map_lookup_elem(&stat_and_limit_k);
+
+ // If we don't have a limit, then abort...
+ if (!limit_v) return TC_ACT_OK;
+
+ // Required IPv6 minimum mtu is 1280, below that not clear what we should do, abort...
+ const int pmtu = v->pmtu;
+ if (pmtu < IPV6_MIN_MTU) return TC_ACT_OK;
+
+ // Approximate handling of TCP/IPv6 overhead for incoming LRO/GRO packets: default
+ // outbound path mtu of 1500 is not necessarily correct, but worst case we simply
+ // undercount, which is still better then not accounting for this overhead at all.
+ // Note: this really shouldn't be device/path mtu at all, but rather should be
+ // derived from this particular connection's mss (ie. from gro segment size).
+ // This would require a much newer kernel with newer ebpf accessors.
+ // (This is also blindly assuming 12 bytes of tcp timestamp option in tcp header)
+ uint64_t packets = 1;
+ uint64_t bytes = skb->len;
+ if (bytes > pmtu) {
+ const int tcp_overhead = sizeof(struct ipv6hdr) + sizeof(struct tcphdr) + 12;
+ const int mss = pmtu - tcp_overhead;
+ const uint64_t payload = bytes - tcp_overhead;
+ packets = (payload + mss - 1) / mss;
+ bytes = tcp_overhead * packets + payload;
+ }
+
+ // Are we past the limit? If so, then abort...
+ // Note: will not overflow since u64 is 936 years even at 5Gbps.
+ // Do not drop here. Offload is just that, whenever we fail to handle
+ // a packet we let the core stack deal with things.
+ // (The core stack needs to handle limits correctly anyway,
+ // since we don't offload all traffic in both directions)
+ if (stat_v->rxBytes + stat_v->txBytes + bytes > *limit_v) return TC_ACT_OK;
+
+ if (!is_ethernet) {
+ is_ethernet = true;
+ l2_header_size = sizeof(struct ethhdr);
+ // Try to inject an ethernet header, and simply return if we fail
+ if (bpf_skb_change_head(skb, l2_header_size, /*flags*/ 0)) {
+ __sync_fetch_and_add(&stat_v->rxErrors, 1);
+ return TC_ACT_OK;
+ }
+
+ // bpf_skb_change_head() invalidates all pointers - reload them
+ data = (void*)(long)skb->data;
+ data_end = (void*)(long)skb->data_end;
+ eth = data;
+ ip6 = (void*)(eth + 1);
+
+ // I do not believe this can ever happen, but keep the verifier happy...
+ if (data + l2_header_size + sizeof(*ip6) > data_end) {
+ __sync_fetch_and_add(&stat_v->rxErrors, 1);
+ return TC_ACT_SHOT;
+ }
+ };
+
+ // CHECKSUM_COMPLETE is a 16-bit one's complement sum,
+ // thus corrections for it need to be done in 16-byte chunks at even offsets.
+ // IPv6 nexthdr is at offset 6, while hop limit is at offset 7
+ uint8_t old_hl = ip6->hop_limit;
+ --ip6->hop_limit;
+ uint8_t new_hl = ip6->hop_limit;
+
+ // bpf_csum_update() always succeeds if the skb is CHECKSUM_COMPLETE and returns an error
+ // (-ENOTSUPP) if it isn't.
+ bpf_csum_update(skb, 0xFFFF - ntohs(old_hl) + ntohs(new_hl));
+
+ __sync_fetch_and_add(&stat_v->rxPackets, packets);
+ __sync_fetch_and_add(&stat_v->rxBytes, bytes);
+
+ // Overwrite any mac header with the new one
+ *eth = v->macHeader;
+
+ // Redirect to forwarded interface.
+ //
+ // Note that bpf_redirect() cannot fail unless you pass invalid flags.
+ // The redirect actually happens after the ebpf program has already terminated,
+ // and can fail for example for mtu reasons at that point in time, but there's nothing
+ // we can do about it here.
+ return bpf_redirect(v->oif, 0 /* this is effectively BPF_F_EGRESS */);
+}
+
+SEC("schedcls/ingress/tether_ether")
+int sched_cls_ingress_tether_ether(struct __sk_buff* skb) {
+ return do_forward(skb, true);
+}
+
+// Note: section names must be unique to prevent programs from appending to each other,
+// so instead the bpf loader will strip everything past the final $ symbol when actually
+// pinning the program into the filesystem.
+//
+// bpf_skb_change_head() is only present on 4.14+ and 2 trivial kernel patches are needed:
+// ANDROID: net: bpf: Allow TC programs to call BPF_FUNC_skb_change_head
+// ANDROID: net: bpf: permit redirect from ingress L3 to egress L2 devices at near max mtu
+// (the first of those has already been upstreamed)
+//
+// 5.4 kernel support was only added to Android Common Kernel in R,
+// and thus a 5.4 kernel always supports this.
+//
+// Hence, this mandatory (must load successfully) implementation for 5.4+ kernels:
+DEFINE_BPF_PROG_KVER("schedcls/ingress/tether_rawip$5_4", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_5_4, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward(skb, false);
+}
+
+// and this identical optional (may fail to load) implementation for [4.14..5.4) patched kernels:
+DEFINE_OPTIONAL_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$4_14", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_4_14, KVER(4, 14, 0),
+ KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return do_forward(skb, false);
+}
+
+// and define a no-op stub for [4.9,4.14) and unpatched [4.14,5.4) kernels.
+// (if the above real 4.14+ program loaded successfully, then bpfloader will have already pinned
+// it at the same location this one would be pinned at and will thus skip loading this stub)
+DEFINE_BPF_PROG_KVER_RANGE("schedcls/ingress/tether_rawip$stub", AID_ROOT, AID_ROOT,
+ sched_cls_ingress_tether_rawip_stub, KVER_NONE, KVER(5, 4, 0))
+(struct __sk_buff* skb) {
+ return TC_ACT_OK;
+}
+
+LICENSE("Apache 2.0");
+CRITICAL("netd");
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index 27b17c3..a10729d 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -17,7 +17,8 @@
name: "framework-tethering",
defaults: ["framework-module-defaults"],
impl_library_visibility: [
- "//packages/modules/Connectivity/Tethering:__subpackages__"
+ "//frameworks/base/packages/Tethering:__subpackages__",
+ "//packages/modules/Connectivity/Tethering:__subpackages__",
],
srcs: [":framework-tethering-srcs"],
diff --git a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
index 8be7964..cf094aa 100644
--- a/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
+++ b/Tethering/common/TetheringLib/src/android/net/ITetheringConnector.aidl
@@ -22,25 +22,31 @@
/** @hide */
oneway interface ITetheringConnector {
- void tether(String iface, String callerPkg, IIntResultListener receiver);
-
- void untether(String iface, String callerPkg, IIntResultListener receiver);
-
- void setUsbTethering(boolean enable, String callerPkg, IIntResultListener receiver);
-
- void startTethering(in TetheringRequestParcel request, String callerPkg,
+ void tether(String iface, String callerPkg, String callingAttributionTag,
IIntResultListener receiver);
- void stopTethering(int type, String callerPkg, IIntResultListener receiver);
+ void untether(String iface, String callerPkg, String callingAttributionTag,
+ IIntResultListener receiver);
+
+ void setUsbTethering(boolean enable, String callerPkg,
+ String callingAttributionTag, IIntResultListener receiver);
+
+ void startTethering(in TetheringRequestParcel request, String callerPkg,
+ String callingAttributionTag, IIntResultListener receiver);
+
+ void stopTethering(int type, String callerPkg, String callingAttributionTag,
+ IIntResultListener receiver);
void requestLatestTetheringEntitlementResult(int type, in ResultReceiver receiver,
- boolean showEntitlementUi, String callerPkg);
+ boolean showEntitlementUi, String callerPkg, String callingAttributionTag);
void registerTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
void unregisterTetheringEventCallback(ITetheringEventCallback callback, String callerPkg);
- void isTetheringSupported(String callerPkg, IIntResultListener receiver);
+ void isTetheringSupported(String callerPkg, String callingAttributionTag,
+ IIntResultListener receiver);
- void stopAllTethering(String callerPkg, IIntResultListener receiver);
+ void stopAllTethering(String callerPkg, String callingAttributionTag,
+ IIntResultListener receiver);
}
diff --git a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index 3267195..13b05a8 100644
--- a/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -489,7 +489,7 @@
return dispatcher.waitForResult((connector, listener) -> {
try {
- connector.tether(iface, callerPkg, listener);
+ connector.tether(iface, callerPkg, getAttributionTag(), listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -497,6 +497,13 @@
}
/**
+ * @return the context's attribution tag
+ */
+ private @Nullable String getAttributionTag() {
+ return mContext.getAttributionTag();
+ }
+
+ /**
* Stop tethering the named interface.
*
* @deprecated The only usages is PanService. It uses this for legacy reasons
@@ -514,7 +521,7 @@
return dispatcher.waitForResult((connector, listener) -> {
try {
- connector.untether(iface, callerPkg, listener);
+ connector.untether(iface, callerPkg, getAttributionTag(), listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -541,7 +548,8 @@
return dispatcher.waitForResult((connector, listener) -> {
try {
- connector.setUsbTethering(enable, callerPkg, listener);
+ connector.setUsbTethering(enable, callerPkg, getAttributionTag(),
+ listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -740,7 +748,8 @@
});
}
};
- getConnector(c -> c.startTethering(request.getParcel(), callerPkg, listener));
+ getConnector(c -> c.startTethering(request.getParcel(), callerPkg,
+ getAttributionTag(), listener));
}
/**
@@ -780,7 +789,8 @@
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "stopTethering caller:" + callerPkg);
- getConnector(c -> c.stopTethering(type, callerPkg, new IIntResultListener.Stub() {
+ getConnector(c -> c.stopTethering(type, callerPkg, getAttributionTag(),
+ new IIntResultListener.Stub() {
@Override
public void onResult(int resultCode) {
// TODO: provide an API to obtain result
@@ -866,7 +876,7 @@
Log.i(TAG, "getLatestTetheringEntitlementResult caller:" + callerPkg);
getConnector(c -> c.requestLatestTetheringEntitlementResult(
- type, receiver, showEntitlementUi, callerPkg));
+ type, receiver, showEntitlementUi, callerPkg, getAttributionTag()));
}
/**
@@ -1317,7 +1327,7 @@
final RequestDispatcher dispatcher = new RequestDispatcher();
final int ret = dispatcher.waitForResult((connector, listener) -> {
try {
- connector.isTetheringSupported(callerPkg, listener);
+ connector.isTetheringSupported(callerPkg, getAttributionTag(), listener);
} catch (RemoteException e) {
throw new IllegalStateException(e);
}
@@ -1340,14 +1350,15 @@
final String callerPkg = mContext.getOpPackageName();
Log.i(TAG, "stopAllTethering caller:" + callerPkg);
- getConnector(c -> c.stopAllTethering(callerPkg, new IIntResultListener.Stub() {
- @Override
- public void onResult(int resultCode) {
- // TODO: add an API parameter to send result to caller.
- // This has never been possible as stopAllTethering has always been void and never
- // taken a callback object. The only indication that callers have is if the call
- // results in a TETHER_STATE_CHANGE broadcast.
- }
- }));
+ getConnector(c -> c.stopAllTethering(callerPkg, getAttributionTag(),
+ new IIntResultListener.Stub() {
+ @Override
+ public void onResult(int resultCode) {
+ // TODO: add an API parameter to send result to caller.
+ // This has never been possible as stopAllTethering has always been void
+ // and never taken a callback object. The only indication that callers have
+ // is if the call results in a TETHER_STATE_CHANGE broadcast.
+ }
+ }));
}
}
diff --git a/Tethering/jarjar-rules.txt b/Tethering/jarjar-rules.txt
index 591861f..d1ad569 100644
--- a/Tethering/jarjar-rules.txt
+++ b/Tethering/jarjar-rules.txt
@@ -8,4 +8,7 @@
rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
# Classes from net-utils-framework-common
-rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
\ No newline at end of file
+rule com.android.net.module.util.** com.android.networkstack.tethering.util.@1
+
+# Classes from net-utils-device-common
+rule com.android.net.module.util.Struct* com.android.networkstack.tethering.util.Struct@1
diff --git a/Tethering/jni/android_net_util_TetheringUtils.cpp b/Tethering/jni/android_net_util_TetheringUtils.cpp
index 7bfb6da..27c84cf 100644
--- a/Tethering/jni/android_net_util_TetheringUtils.cpp
+++ b/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -28,9 +28,6 @@
#include <sys/socket.h>
#include <stdio.h>
-#define LOG_TAG "TetheringUtils"
-#include <android/log.h>
-
namespace android {
static const uint32_t kIPv6NextHeaderOffset = offsetof(ip6_hdr, ip6_nxt);
@@ -184,18 +181,4 @@
gMethods, NELEM(gMethods));
}
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
- JNIEnv *env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
- return JNI_ERR;
- }
-
- if (register_android_net_util_TetheringUtils(env) < 0) {
- return JNI_ERR;
- }
-
- return JNI_VERSION_1_6;
-}
-
}; // namespace android
diff --git a/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
new file mode 100644
index 0000000..eadc210
--- /dev/null
+++ b/Tethering/jni/com_android_networkstack_tethering_BpfMap.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "nativehelper/scoped_primitive_array.h"
+#include "nativehelper/scoped_utf_chars.h"
+
+#define BPF_FD_JUST_USE_INT
+#include "BpfSyscallWrappers.h"
+
+namespace android {
+
+static jclass sErrnoExceptionClass;
+static jmethodID sErrnoExceptionCtor2;
+static jmethodID sErrnoExceptionCtor3;
+
+static void throwErrnoException(JNIEnv* env, const char* functionName, int error) {
+ if (sErrnoExceptionClass == nullptr || sErrnoExceptionClass == nullptr) return;
+
+ jthrowable cause = nullptr;
+ if (env->ExceptionCheck()) {
+ cause = env->ExceptionOccurred();
+ env->ExceptionClear();
+ }
+
+ ScopedLocalRef<jstring> msg(env, env->NewStringUTF(functionName));
+
+ // Not really much we can do here if msg is null, let's try to stumble on...
+ if (msg.get() == nullptr) env->ExceptionClear();
+
+ jobject errnoException;
+ if (cause != nullptr) {
+ errnoException = env->NewObject(sErrnoExceptionClass, sErrnoExceptionCtor3, msg.get(),
+ error, cause);
+ } else {
+ errnoException = env->NewObject(sErrnoExceptionClass, sErrnoExceptionCtor2, msg.get(),
+ error);
+ }
+ env->Throw(static_cast<jthrowable>(errnoException));
+}
+
+static jint com_android_networkstack_tethering_BpfMap_closeMap(JNIEnv *env, jobject clazz,
+ jint fd) {
+ int ret = close(fd);
+
+ if (ret) throwErrnoException(env, "closeMap", errno);
+
+ return ret;
+}
+
+static jint com_android_networkstack_tethering_BpfMap_bpfFdGet(JNIEnv *env, jobject clazz,
+ jstring path, jint mode) {
+ ScopedUtfChars pathname(env, path);
+
+ jint fd = bpf::bpfFdGet(pathname.c_str(), static_cast<unsigned>(mode));
+
+ return fd;
+}
+
+static void com_android_networkstack_tethering_BpfMap_writeToMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value, jint flags) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRO valueRO(env, value);
+
+ int ret = bpf::writeToMapEntry(static_cast<int>(fd), keyRO.get(), valueRO.get(),
+ static_cast<int>(flags));
+
+ if (ret) throwErrnoException(env, "writeToMapEntry", errno);
+}
+
+static jboolean throwIfNotEnoent(JNIEnv *env, const char* functionName, int ret, int err) {
+ if (ret == 0) return true;
+
+ if (err != ENOENT) throwErrnoException(env, functionName, err);
+ return false;
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_deleteMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key) {
+ ScopedByteArrayRO keyRO(env, key);
+
+ // On success, zero is returned. If the element is not found, -1 is returned and errno is set
+ // to ENOENT.
+ int ret = bpf::deleteMapEntry(static_cast<int>(fd), keyRO.get());
+
+ return throwIfNotEnoent(env, "deleteMapEntry", ret, errno);
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_getNextMapKey(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray nextKey) {
+ // If key is found, the operation returns zero and sets the next key pointer to the key of the
+ // next element. If key is not found, the operation returns zero and sets the next key pointer
+ // to the key of the first element. If key is the last element, -1 is returned and errno is
+ // set to ENOENT. Other possible errno values are ENOMEM, EFAULT, EPERM, and EINVAL.
+ ScopedByteArrayRW nextKeyRW(env, nextKey);
+ int ret;
+ if (key == nullptr) {
+ // Called by getFirstKey. Find the first key in the map.
+ ret = bpf::getNextMapKey(static_cast<int>(fd), nullptr, nextKeyRW.get());
+ } else {
+ ScopedByteArrayRO keyRO(env, key);
+ ret = bpf::getNextMapKey(static_cast<int>(fd), keyRO.get(), nextKeyRW.get());
+ }
+
+ return throwIfNotEnoent(env, "getNextMapKey", ret, errno);
+}
+
+static jboolean com_android_networkstack_tethering_BpfMap_findMapEntry(JNIEnv *env, jobject clazz,
+ jint fd, jbyteArray key, jbyteArray value) {
+ ScopedByteArrayRO keyRO(env, key);
+ ScopedByteArrayRW valueRW(env, value);
+
+ // If an element is found, the operation returns zero and stores the element's value into
+ // "value". If no element is found, the operation returns -1 and sets errno to ENOENT.
+ int ret = bpf::findMapEntry(static_cast<int>(fd), keyRO.get(), valueRW.get());
+
+ return throwIfNotEnoent(env, "findMapEntry", ret, errno);
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "closeMap", "(I)I",
+ (void*) com_android_networkstack_tethering_BpfMap_closeMap },
+ { "bpfFdGet", "(Ljava/lang/String;I)I",
+ (void*) com_android_networkstack_tethering_BpfMap_bpfFdGet },
+ { "writeToMapEntry", "(I[B[BI)V",
+ (void*) com_android_networkstack_tethering_BpfMap_writeToMapEntry },
+ { "deleteMapEntry", "(I[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_deleteMapEntry },
+ { "getNextMapKey", "(I[B[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_getNextMapKey },
+ { "findMapEntry", "(I[B[B)Z",
+ (void*) com_android_networkstack_tethering_BpfMap_findMapEntry },
+
+};
+
+int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env) {
+ sErrnoExceptionClass = static_cast<jclass>(env->NewGlobalRef(
+ env->FindClass("android/system/ErrnoException")));
+ if (sErrnoExceptionClass == nullptr) return JNI_ERR;
+
+ sErrnoExceptionCtor2 = env->GetMethodID(sErrnoExceptionClass, "<init>",
+ "(Ljava/lang/String;I)V");
+ if (sErrnoExceptionCtor2 == nullptr) return JNI_ERR;
+
+ sErrnoExceptionCtor3 = env->GetMethodID(sErrnoExceptionClass, "<init>",
+ "(Ljava/lang/String;ILjava/lang/Throwable;)V");
+ if (sErrnoExceptionCtor3 == nullptr) return JNI_ERR;
+
+ return jniRegisterNativeMethods(env,
+ "com/android/networkstack/tethering/BpfMap",
+ gMethods, NELEM(gMethods));
+}
+
+}; // namespace android
diff --git a/Tethering/jni/onload.cpp b/Tethering/jni/onload.cpp
new file mode 100644
index 0000000..3766de9
--- /dev/null
+++ b/Tethering/jni/onload.cpp
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include "jni.h"
+
+#define LOG_TAG "TetheringJni"
+#include <android/log.h>
+
+namespace android {
+
+int register_android_net_util_TetheringUtils(JNIEnv* env);
+int register_com_android_networkstack_tethering_BpfMap(JNIEnv* env);
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_util_TetheringUtils(env) < 0) return JNI_ERR;
+
+ if (register_com_android_networkstack_tethering_BpfMap(env) < 0) return JNI_ERR;
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/Tethering/proguard.flags b/Tethering/proguard.flags
index 86b9033..9ab56c2 100644
--- a/Tethering/proguard.flags
+++ b/Tethering/proguard.flags
@@ -4,6 +4,14 @@
static final int EVENT_*;
}
+-keep class com.android.networkstack.tethering.BpfMap {
+ native <methods>;
+}
+
+-keepclassmembers public class * extends com.android.networkstack.tethering.util.Struct {
+ public <init>(...);
+}
+
-keepclassmembers class android.net.ip.IpServer {
static final int CMD_*;
}
diff --git a/Tethering/res/values-mcc204-mnc04-af/strings.xml b/Tethering/res/values-mcc204-mnc04-af/strings.xml
new file mode 100644
index 0000000..052ca09
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-af/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Warmkol het nie internet nie"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Toestelle kan nie aan internet koppel nie"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Skakel warmkol af"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Warmkol is aan"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Bykomende heffings kan geld terwyl jy swerf"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Gaan voort"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-am/strings.xml b/Tethering/res/values-mcc204-mnc04-am/strings.xml
new file mode 100644
index 0000000..0518c5a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-am/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"መገናኛ ነጥቡ በይነመረብ የለውም"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"መሣሪያዎች ከበይነመረብ ጋር መገናኘት አይችሉም"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"መገናኛ ነጥብ ያጥፉ"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"የመገናኛ ነጥብ በርቷል"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ቀጥል"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ar/strings.xml b/Tethering/res/values-mcc204-mnc04-ar/strings.xml
new file mode 100644
index 0000000..e6d8423
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ar/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"نقطة الاتصال غير متصلة بالإنترنت."</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"لا يمكن للأجهزة الاتصال بالإنترنت."</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"إيقاف نقطة الاتصال"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"نقطة الاتصال مفعّلة"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"قد يتم تطبيق رسوم إضافية أثناء التجوال."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"متابعة"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-as/strings.xml b/Tethering/res/values-mcc204-mnc04-as/strings.xml
new file mode 100644
index 0000000..4c57f21
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-as/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"হটস্পটৰ কোনো ইণ্টাৰনেট নাই"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ডিভাইচসমূহ ইণ্টাৰনেটৰ সৈতে সংযোগ কৰিব নোৱাৰি"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"হটস্পট অফ কৰক"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"হটস্পট অন হৈ আছে"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"অব্যাহত ৰাখক"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-az/strings.xml b/Tethering/res/values-mcc204-mnc04-az/strings.xml
new file mode 100644
index 0000000..2610ab1
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-az/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspotun internetə girişi yoxdur"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Cihazlar internetə qoşula bilmir"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspot\'u deaktiv edin"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot aktivdir"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Davam edin"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml b/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..7b032ba
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-b+sr+Latn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot nema pristup internetu"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Uređaji ne mogu da se povežu na internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Isključi hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot je uključen"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Možda važe dodatni troškovi u romingu"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Nastavi"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-be/strings.xml b/Tethering/res/values-mcc204-mnc04-be/strings.xml
new file mode 100644
index 0000000..2362a1e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-be/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Хот-спот не падключаны да інтэрнэту"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Прылады не могуць падключацца да інтэрнэту"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Выключыць хот-спот"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Хот-спот уключаны"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Працягнуць"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-bg/strings.xml b/Tethering/res/values-mcc204-mnc04-bg/strings.xml
new file mode 100644
index 0000000..6ef1b0b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bg/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Точката за достъп няма връзка с интернет"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Устройствата не могат да се свържат с интернет"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Изключване на точката за достъп"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Точката за достъп е включена"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Възможно е да ви бъдат начислени допълнителни такси при роуминг"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Напред"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-bn/strings.xml b/Tethering/res/values-mcc204-mnc04-bn/strings.xml
new file mode 100644
index 0000000..9a3033c
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"হটস্পটের সাথে ইন্টারনেট কানেক্ট করা নেই"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ডিভাইস ইন্টারনেটের সাথে কানেক্ট করতে পারছে না"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"হটস্পট বন্ধ করুন"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"হটস্পট চালু আছে"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"চালিয়ে যান"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-bs/strings.xml b/Tethering/res/values-mcc204-mnc04-bs/strings.xml
new file mode 100644
index 0000000..57f6d88
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-bs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Pristupna tačka nema internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Uređaji se ne mogu povezati na internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Isključi pristupnu tačku"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Pristupna tačka je uključena"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Mogu nastati dodatni troškovi u romingu"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Nastavi"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ca/strings.xml b/Tethering/res/values-mcc204-mnc04-ca/strings.xml
new file mode 100644
index 0000000..e3ad666
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ca/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"El punt d\'accés Wi‑Fi no té accés a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Els dispositius no es poden connectar a Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desactiva el punt d\'accés Wi‑Fi"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"El punt d\'accés Wi‑Fi està activat"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"És possible que s\'apliquin costos addicionals en itinerància"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continua"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-cs/strings.xml b/Tethering/res/values-mcc204-mnc04-cs/strings.xml
new file mode 100644
index 0000000..f099281
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-cs/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot nemá připojení k internetu"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Zařízení se nemohou připojit k internetu"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Vypnout hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot je aktivní"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Při roamingu mohou být účtovány dodatečné poplatky"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Pokračovat"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-da/strings.xml b/Tethering/res/values-mcc204-mnc04-da/strings.xml
new file mode 100644
index 0000000..1fb2374
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-da/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspottet har intet internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Enheder kan ikke oprette forbindelse til internettet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Deaktiver hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspottet er aktiveret"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Der opkræves muligvis yderligere gebyrer ved roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Fortsæt"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-de/strings.xml b/Tethering/res/values-mcc204-mnc04-de/strings.xml
new file mode 100644
index 0000000..56d1d1d
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-de/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot ist nicht mit dem Internet verbunden"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Geräte können nicht mit dem Internet verbunden werden"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspot deaktivieren"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot aktiviert"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Für das Roaming können zusätzliche Gebühren anfallen"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Weiter"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-el/strings.xml b/Tethering/res/values-mcc204-mnc04-el/strings.xml
new file mode 100644
index 0000000..674f1f6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-el/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Το σημείο πρόσβασης Wi-Fi δεν έχει πρόσβαση στο διαδίκτυο."</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Δεν είναι η δυνατή η σύνδεση των συσκευών στο διαδίκτυο."</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Απενεργοποίηση σημείου πρόσβασης Wi-Fi"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Σημείο πρόσβασης Wi-Fi ενεργό"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Συνέχεια"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml
new file mode 100644
index 0000000..3046a37
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rAU/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot has no Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Devices can’t connect to Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Turn off hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is on"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Additional charges may apply while roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continue"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml
new file mode 100644
index 0000000..3046a37
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot has no Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Devices can’t connect to Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Turn off hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is on"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Additional charges may apply while roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continue"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml
new file mode 100644
index 0000000..3046a37
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rGB/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot has no Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Devices can’t connect to Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Turn off hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is on"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Additional charges may apply while roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continue"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml
new file mode 100644
index 0000000..3046a37
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rIN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot has no Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Devices can’t connect to Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Turn off hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is on"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Additional charges may apply while roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continue"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml b/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml
new file mode 100644
index 0000000..20c9b94
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-en-rXC/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot has no internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Devices can’t connect to internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Turn off hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is on"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Additional charges may apply while roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continue"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml b/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml
new file mode 100644
index 0000000..956547c
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-es-rUS/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"El hotspot no tiene conexión a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Los dispositivos no pueden conectarse a Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desactiva el hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"El hotspot está activado"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Es posible que se apliquen cargos adicionales por roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-es/strings.xml b/Tethering/res/values-mcc204-mnc04-es/strings.xml
new file mode 100644
index 0000000..831ec1f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-es/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"El punto de acceso no tiene conexión a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Los dispositivos no se pueden conectar a Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desactivar punto de acceso"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Punto de acceso activado"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Puede que se apliquen cargos adicionales en itinerancia"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-et/strings.xml b/Tethering/res/values-mcc204-mnc04-et/strings.xml
new file mode 100644
index 0000000..ff8dde5
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-et/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Kuumkohal puudub Interneti-ühendus"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Seadmed ei saa Internetiga ühendust luua"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Lülita kuumkoht välja"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Kuumkoht on sees"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Rändluse kasutamisega võivad kaasneda lisatasud"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Jätka"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-eu/strings.xml b/Tethering/res/values-mcc204-mnc04-eu/strings.xml
new file mode 100644
index 0000000..c4f70a3
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-eu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Sare publikoak ez du Interneteko konexiorik"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Gailuak ezin dira konektatu Internetera"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desaktibatu sare publikoa"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Sare publikoa aktibatuta dago"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Egin aurrera"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-fa/strings.xml b/Tethering/res/values-mcc204-mnc04-fa/strings.xml
new file mode 100644
index 0000000..79e3ef1
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"نقطه اتصال به اینترنت دسترسی ندارد"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"دستگاهها به اینترنت متصل نشدند"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"نقطه اتصال را خاموش کنید"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"نقطه اتصال روشن است"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ممکن است درحین فراگردی تغییرات دیگر اعمال شود"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ادامه"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-fi/strings.xml b/Tethering/res/values-mcc204-mnc04-fi/strings.xml
new file mode 100644
index 0000000..64921bc
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspotilla ei ole internetyhteyttä"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Laitteet eivät voi yhdistää internetiin"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Laita hotspot pois päältä"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot on päällä"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Roaming voi aiheuttaa lisämaksuja"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Jatka"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml b/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml
new file mode 100644
index 0000000..eda7b59
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fr-rCA/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Le point d\'accès n\'est pas connecté à Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Appareils non connectés à Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Désactiver le point d\'accès"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Le point d\'accès est activé"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuer"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-fr/strings.xml b/Tethering/res/values-mcc204-mnc04-fr/strings.xml
new file mode 100644
index 0000000..eda7b59
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-fr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Le point d\'accès n\'est pas connecté à Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Appareils non connectés à Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Désactiver le point d\'accès"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Le point d\'accès est activé"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"En itinérance, des frais supplémentaires peuvent s\'appliquer"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuer"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-gl/strings.xml b/Tethering/res/values-mcc204-mnc04-gl/strings.xml
new file mode 100644
index 0000000..c163c61
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-gl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"A zona wifi non ten acceso a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Os dispositivos non se poden conectar a Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desactivar zona wifi"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"A zona wifi está activada"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Pódense aplicar cargos adicionais en itinerancia"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-gu/strings.xml b/Tethering/res/values-mcc204-mnc04-gu/strings.xml
new file mode 100644
index 0000000..796d42e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-gu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"હૉટસ્પૉટથી ઇન્ટરનેટ ચાલી રહ્યું નથી"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ડિવાઇસ, ઇન્ટરનેટ સાથે કનેક્ટ થઈ શકતા નથી"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"હૉટસ્પૉટ બંધ કરો"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"હૉટસ્પૉટ ચાલુ છે"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"આગળ વધો"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-hi/strings.xml b/Tethering/res/values-mcc204-mnc04-hi/strings.xml
new file mode 100644
index 0000000..a244200
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"हॉटस्पॉट से इंटरनेट नहीं चल रहा"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"डिवाइस इंटरनेट से कनेक्ट नहीं हो पा रहे"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"हॉटस्पॉट बंद करें"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"हॉटस्पॉट चालू है"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"जारी रखें"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-hr/strings.xml b/Tethering/res/values-mcc204-mnc04-hr/strings.xml
new file mode 100644
index 0000000..41618af
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Žarišna točka nema pristup internetu"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Uređaji se ne mogu povezati s internetom"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Isključi žarišnu točku"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Žarišna je točka uključena"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"U roamingu su mogući dodatni troškovi"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Nastavi"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-hu/strings.xml b/Tethering/res/values-mcc204-mnc04-hu/strings.xml
new file mode 100644
index 0000000..39b7a69
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"A hotspot nem csatlakozik az internethez"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Az eszközök nem tudnak csatlakozni az internethez"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspot kikapcsolása"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"A hotspot be van kapcsolva"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Roaming során további díjak léphetnek fel"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Tovább"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-hy/strings.xml b/Tethering/res/values-mcc204-mnc04-hy/strings.xml
new file mode 100644
index 0000000..c14ae10
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-hy/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Թեժ կետը միացված չէ ինտերնետին"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Սարքերը չեն կարողանում միանալ ինտերնետին"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Անջատել թեժ կետը"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Թեժ կետը միացված է"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Շարունակել"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-in/strings.xml b/Tethering/res/values-mcc204-mnc04-in/strings.xml
new file mode 100644
index 0000000..1243d22
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-in/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot tidak memiliki koneksi internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Perangkat tidak dapat tersambung ke internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Nonaktifkan hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot aktif"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Biaya tambahan mungkin berlaku saat roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Lanjutkan"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-is/strings.xml b/Tethering/res/values-mcc204-mnc04-is/strings.xml
new file mode 100644
index 0000000..82a7d01
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-is/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Heitur reitur er ekki nettengdur"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Tæki geta ekki tengst við internetið"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Slökkva á heitum reit"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Kveikt er á heitum reit"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Viðbótargjöld kunna að eiga við í reiki"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Halda áfram"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-it/strings.xml b/Tethering/res/values-mcc204-mnc04-it/strings.xml
new file mode 100644
index 0000000..a0f52dc
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-it/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"L\'hotspot non ha accesso a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"I dispositivi non possono connettersi a Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Disattiva l\'hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot attivo"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Potrebbero essere applicati costi aggiuntivi durante il roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continua"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-iw/strings.xml b/Tethering/res/values-mcc204-mnc04-iw/strings.xml
new file mode 100644
index 0000000..80807bc
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-iw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"לנקודה לשיתוף אינטרנט אין חיבור לאינטרנט"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"המכשירים לא יכולים להתחבר לאינטרנט"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"כיבוי הנקודה לשיתוף אינטרנט"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"הנקודה לשיתוף אינטרנט פועלת"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ייתכנו חיובים נוספים בעת נדידה"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"המשך"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ja/strings.xml b/Tethering/res/values-mcc204-mnc04-ja/strings.xml
new file mode 100644
index 0000000..0e21a7f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ja/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"アクセス ポイントがインターネットに接続されていません"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"デバイスをインターネットに接続できません"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"アクセス ポイントを OFF にする"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"アクセス ポイント: ON"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ローミング時に追加料金が発生することがあります"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"続行"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ka/strings.xml b/Tethering/res/values-mcc204-mnc04-ka/strings.xml
new file mode 100644
index 0000000..6d3b548
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ka/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"უსადენო ქსელს არ აქვს ინტერნეტზე წვდომა"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"მოწყობილობები ვერ უკავშირდება ინტერნეტს"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"გამორთეთ უსადენო ქსელი"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"უსადენო ქსელი ჩართულია"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"გაგრძელება"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-kk/strings.xml b/Tethering/res/values-mcc204-mnc04-kk/strings.xml
new file mode 100644
index 0000000..985fc3f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-kk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Хотспотта интернет жоқ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Құрылғылар интернетке қосылмайды"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Хотспотты өшіру"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Хотспот қосулы"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Роуминг кезінде қосымша ақы алынуы мүмкін."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Жалғастыру"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-km/strings.xml b/Tethering/res/values-mcc204-mnc04-km/strings.xml
new file mode 100644
index 0000000..03b5cb6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-km/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ហតស្ប៉តមិនមានអ៊ីនធឺណិតទេ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ឧបករណ៍មិនអាចភ្ជាប់អ៊ីនធឺណិតបានទេ"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"បិទហតស្ប៉ត"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ហតស្ប៉តត្រូវបានបើក"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"អាចមានការគិតថ្លៃបន្ថែម នៅពេលរ៉ូមីង"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"បន្ត"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-kn/strings.xml b/Tethering/res/values-mcc204-mnc04-kn/strings.xml
new file mode 100644
index 0000000..f0adad8
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-kn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ಹಾಟ್ಸ್ಪಾಟ್ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಸಂಪರ್ಕವನ್ನು ಹೊಂದಿಲ್ಲ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ಇಂಟರ್ನೆಟ್ಗೆ ಸಂಪರ್ಕಗೊಳ್ಳಲು ಸಾಧನಗಳಿಗೆ ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ಹಾಟ್ಸ್ಪಾಟ್ ಆಫ್ ಮಾಡಿ"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ಹಾಟ್ಸ್ಪಾಟ್ ಆನ್ ಆಗಿದೆ"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ರೋಮಿಂಗ್ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ಮುಂದುವರಿಸಿ"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ko/strings.xml b/Tethering/res/values-mcc204-mnc04-ko/strings.xml
new file mode 100644
index 0000000..9218e9a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ko/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"핫스팟이 인터넷에 연결되지 않음"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"기기를 인터넷에 연결할 수 없음"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"핫스팟 사용 중지"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"핫스팟 사용 중"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"로밍 중에는 추가 요금이 발생할 수 있습니다."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"계속"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ky/strings.xml b/Tethering/res/values-mcc204-mnc04-ky/strings.xml
new file mode 100644
index 0000000..35a060a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ky/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Байланыш түйүнүндө Интернет жок"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Түзмөктөр Интернетке туташпай жатат"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Туташуу түйүнүн өчүрүү"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Кошулуу түйүнү күйүк"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Роумингде кошумча акы алынышы мүмкүн"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Улантуу"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-lo/strings.xml b/Tethering/res/values-mcc204-mnc04-lo/strings.xml
new file mode 100644
index 0000000..1d9203b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lo/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ຮັອດສະປອດບໍ່ມີອິນເຕີເນັດ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ອິນເຕີເນັດໄດ້"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ປິດຮັອດສະປອດ"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ຮັອດສະປອດເປີດຢູ່"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ສືບຕໍ່"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-lt/strings.xml b/Tethering/res/values-mcc204-mnc04-lt/strings.xml
new file mode 100644
index 0000000..db5178b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Nėra viešosios interneto prieigos taško interneto ryšio"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Įrenginiams nepavyksta prisijungti prie interneto"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Išjungti viešosios interneto prieigos tašką"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Viešosios interneto prieigos taškas įjungtas"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Tęsti"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-lv/strings.xml b/Tethering/res/values-mcc204-mnc04-lv/strings.xml
new file mode 100644
index 0000000..c712173
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-lv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Tīklājam nav interneta savienojuma"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Ierīces nevar izveidot savienojumu ar internetu"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Izslēgt tīklāju"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Tīklājs ir ieslēgts"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Viesabonēšanas laikā var tikt piemērota papildu samaksa"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Tālāk"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-mk/strings.xml b/Tethering/res/values-mcc204-mnc04-mk/strings.xml
new file mode 100644
index 0000000..aa44909
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Точката на пристап нема интернет"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Уредите не може да се поврзат на интернет"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Исклучи ја точката на пристап"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Точката на пристап е вклучена"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"При роаминг може да се наплатат дополнителни трошоци"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Продолжи"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ml/strings.xml b/Tethering/res/values-mcc204-mnc04-ml/strings.xml
new file mode 100644
index 0000000..d376fe5
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ml/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ഹോട്ട്സ്പോട്ടിൽ ഇന്റർനെറ്റ് ലഭ്യമല്ല"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ഉപകരണങ്ങൾ ഇന്റർനെറ്റിലേക്ക് കണക്റ്റ് ചെയ്യാനാവില്ല"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ഹോട്ട്സ്പോട്ട് ഓഫാക്കുക"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ഹോട്ട്സ്പോട്ട് ഓണാണ്"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"തുടരുക"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-mn/strings.xml b/Tethering/res/values-mcc204-mnc04-mn/strings.xml
new file mode 100644
index 0000000..417213f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mn/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Сүлжээний цэг дээр интернэт алга байна"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Төхөөрөмжүүд нь интернэтэд холбогдох боломжгүй байна"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Сүлжээний цэгийг унтраах"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Сүлжээний цэг асаалттай байна"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Үргэлжлүүлэх"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-mr/strings.xml b/Tethering/res/values-mcc204-mnc04-mr/strings.xml
new file mode 100644
index 0000000..2ed153fb1
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-mr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"हॉटस्पॉटला इंटरनेट नाही"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"डिव्हाइस इंटरनेटला कनेक्ट करू शकत नाहीत"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"हॉटस्पॉट बंद करा"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"हॉटस्पॉट सुरू आहे"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"सुरू ठेवा"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ms/strings.xml b/Tethering/res/values-mcc204-mnc04-ms/strings.xml
new file mode 100644
index 0000000..50817fd
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ms/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Tempat liputan tiada Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Peranti tidak dapat menyambung kepada Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Matikan tempat liputan"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Tempat liputan dihidupkan"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Caj tambahan mungkin digunakan semasa perayauan"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Teruskan"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-my/strings.xml b/Tethering/res/values-mcc204-mnc04-my/strings.xml
new file mode 100644
index 0000000..c0d70e3
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-my/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ဟော့စပေါ့တွင် အင်တာနက်မရှိပါ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"စက်များက အင်တာနက်ချိတ်ဆက်၍ မရပါ"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ဟော့စပေါ့ ပိတ်ရန်"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ဟော့စပေါ့ ဖွင့်ထားသည်"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ရှေ့ဆက်ရန်"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-nb/strings.xml b/Tethering/res/values-mcc204-mnc04-nb/strings.xml
new file mode 100644
index 0000000..1e7f1c6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-nb/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Wi-Fi-sonen har ikke internettilgang"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Enheter kan ikke koble til internett"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Slå av Wi-Fi-sonen"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Wi-Fi-sonen er på"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Ytterligere kostnader kan påløpe under roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Fortsett"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ne/strings.xml b/Tethering/res/values-mcc204-mnc04-ne/strings.xml
new file mode 100644
index 0000000..63ce155
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ne/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"हटस्पटमा इन्टरनेट छैन"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"यन्त्रहरू इन्टरनेटमा कनेक्ट गर्न सकिएन"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"हटस्पट निष्क्रिय पार्नुहोस्"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"हटस्पट सक्रिय छ"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"जारी राख्नुहोस्"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-nl/strings.xml b/Tethering/res/values-mcc204-mnc04-nl/strings.xml
new file mode 100644
index 0000000..bf14a0f
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-nl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot heeft geen internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Apparaten kunnen geen verbinding maken met internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspot uitschakelen"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot is ingeschakeld"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Er kunnen extra kosten voor roaming in rekening worden gebracht."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Doorgaan"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-or/strings.xml b/Tethering/res/values-mcc204-mnc04-or/strings.xml
new file mode 100644
index 0000000..ab87b76
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-or/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ହଟସ୍ପଟରେ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ଡିଭାଇସଗୁଡ଼ିକ ଇଣ୍ଟର୍ନେଟ୍ ସହ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ହଟସ୍ପଟ ବନ୍ଦ କରନ୍ତୁ"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ହଟସ୍ପଟ ଚାଲୁ ଅଛି"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ଜାରି ରଖନ୍ତୁ"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-pa/strings.xml b/Tethering/res/values-mcc204-mnc04-pa/strings.xml
new file mode 100644
index 0000000..b09f285
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pa/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ਹੌਟਸਪੌਟ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"ਡੀਵਾਈਸ ਇੰਟਰਨੈੱਟ ਨਾਲ ਕਨੈਕਟ ਨਹੀਂ ਹੋ ਸਕਦੇ"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ਹੌਟਸਪੌਟ ਬੰਦ ਕਰੋ"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ਹੌਟਸਪੌਟ ਚਾਲੂ ਹੈ"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ਜਾਰੀ ਰੱਖੋ"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-pl/strings.xml b/Tethering/res/values-mcc204-mnc04-pl/strings.xml
new file mode 100644
index 0000000..8becd07
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot nie ma internetu"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Urządzenia nie mogą połączyć się z internetem"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Wyłącz hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot jest włączony"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Dalej"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml b/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml
new file mode 100644
index 0000000..8e01736
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt-rBR/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"O ponto de acesso não tem conexão com a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Não foi possível conectar os dispositivos à Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desativar ponto de acesso"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"O ponto de acesso está ativado"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Pode haver cobranças extras durante o roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml b/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml
new file mode 100644
index 0000000..2356379
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt-rPT/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"A zona Wi-Fi não tem Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Não é possível ligar os dispositivos à Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desativar zona Wi-Fi"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"A zona Wi-Fi está ativada"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Podem aplicar-se custos adicionais em roaming."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-pt/strings.xml b/Tethering/res/values-mcc204-mnc04-pt/strings.xml
new file mode 100644
index 0000000..8e01736
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-pt/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"O ponto de acesso não tem conexão com a Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Não foi possível conectar os dispositivos à Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Desativar ponto de acesso"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"O ponto de acesso está ativado"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Pode haver cobranças extras durante o roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuar"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ro/strings.xml b/Tethering/res/values-mcc204-mnc04-ro/strings.xml
new file mode 100644
index 0000000..2e62bd6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ro/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspotul nu are internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Dispozitivele nu se pot conecta la internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Dezactivați hotspotul"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspotul este activ"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Se pot aplica taxe suplimentare pentru roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Continuați"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ru/strings.xml b/Tethering/res/values-mcc204-mnc04-ru/strings.xml
new file mode 100644
index 0000000..a2b1640
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ru/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Точка доступа не подключена к Интернету"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Устройства не могут подключаться к Интернету"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Отключить точку доступа"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Точка доступа включена"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"За использование услуг связи в роуминге может взиматься дополнительная плата."</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Продолжить"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-si/strings.xml b/Tethering/res/values-mcc204-mnc04-si/strings.xml
new file mode 100644
index 0000000..632748a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-si/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"හොට්ස්පොට් හට අන්තර්ජාලය නැත"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"උපාංගවලට අන්තර්ජාලයට සම්බන්ධ විය නොහැකිය"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"හොට්ස්පොට් ක්රියාවිරහිත කරන්න"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"හොට්ස්පොට් ක්රියාත්මකයි"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ඉදිරියට යන්න"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sk/strings.xml b/Tethering/res/values-mcc204-mnc04-sk/strings.xml
new file mode 100644
index 0000000..247fc1b
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot nemá internetové pripojenie"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Zariadenia sa nedajú pripojiť k internetu"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Vypnúť hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot je zapnutý"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Počas roamingu vám môžu byť účtované ďalšie poplatky"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Pokračovať"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sl/strings.xml b/Tethering/res/values-mcc204-mnc04-sl/strings.xml
new file mode 100644
index 0000000..ed22372
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Dostopna točka nima internetne povezave"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Naprave ne morejo vzpostaviti internetne povezave"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Izklopi dostopno točko"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Dostopna točka je vklopljena"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Med gostovanjem lahko nastanejo dodatni stroški"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Naprej"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sq/strings.xml b/Tethering/res/values-mcc204-mnc04-sq/strings.xml
new file mode 100644
index 0000000..4bfab6e
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sq/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Zona e qasjes për internet nuk ka internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Pajisjet nuk mund të lidhen me internetin"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Çaktivizo zonën e qasjes për internet"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Zona e qasjes për internet është aktive"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Mund të zbatohen tarifime shtesë kur je në roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Vazhdo"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sr/strings.xml b/Tethering/res/values-mcc204-mnc04-sr/strings.xml
new file mode 100644
index 0000000..478d53a
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Хотспот нема приступ интернету"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Уређаји не могу да се повежу на интернет"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Искључи хотспот"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Хотспот је укључен"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Можда важе додатни трошкови у ромингу"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Настави"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sv/strings.xml b/Tethering/res/values-mcc204-mnc04-sv/strings.xml
new file mode 100644
index 0000000..a793ed6
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sv/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Surfzonen har ingen internetanslutning"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Enheterna har ingen internetanslutning"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Inaktivera surfzon"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Surfzonen är aktiverad"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Ytterligare avgifter kan tillkomma vid roaming"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Fortsätt"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-sw/strings.xml b/Tethering/res/values-mcc204-mnc04-sw/strings.xml
new file mode 100644
index 0000000..18ee457
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-sw/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Mtandao pepe hauna intaneti"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Vifaa vimeshindwa kuunganisha kwenye intaneti"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Zima mtandao pepe"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Mtandao pepe umewashwa"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Endelea"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ta/strings.xml b/Tethering/res/values-mcc204-mnc04-ta/strings.xml
new file mode 100644
index 0000000..7eebd67
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ta/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ஹாட்ஸ்பாட்டில் இணையம் இல்லை"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"சாதனங்களால் இணையத்தில் இணைய இயலவில்லை"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ஹாட்ஸ்பாட்டை ஆஃப் செய்"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ஹாட்ஸ்பாட் ஆன் செய்யப்பட்டுள்ளது"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"தொடர்க"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-te/strings.xml b/Tethering/res/values-mcc204-mnc04-te/strings.xml
new file mode 100644
index 0000000..0986534
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-te/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"హాట్స్పాట్కు ఇంటర్నెట్ యాక్సెస్ లేదు"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"పరికరాలను ఇంటర్నెట్కి కనెక్ట్ చేయడం సాధ్యం కాదు"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"హాట్స్పాట్ని ఆఫ్ చేయండి"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"హాట్స్పాట్ ఆన్లో ఉంది"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"రోమింగ్లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"కొనసాగించు"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-th/strings.xml b/Tethering/res/values-mcc204-mnc04-th/strings.xml
new file mode 100644
index 0000000..3837002
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-th/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ฮอตสปอตไม่ได้เชื่อมต่ออินเทอร์เน็ต"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"อุปกรณ์เชื่อมต่ออินเทอร์เน็ตไม่ได้"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ปิดฮอตสปอต"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ฮอตสปอตเปิดอยู่"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"ต่อไป"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-tl/strings.xml b/Tethering/res/values-mcc204-mnc04-tl/strings.xml
new file mode 100644
index 0000000..208f893
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-tl/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Walang internet ang hotspot"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Hindi makakonekta sa internet ang mga device"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"I-off ang hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Naka-on ang hotspot"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Posibleng magkaroon ng mga karagdagang singil habang nagro-roam"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Ituloy"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-tr/strings.xml b/Tethering/res/values-mcc204-mnc04-tr/strings.xml
new file mode 100644
index 0000000..3482faf
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-tr/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot\'un internet bağlantısı yok"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Cihazlar internete bağlanamıyor"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspot\'u kapat"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot açık"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Dolaşım sırasında ek ücretler uygulanabilir"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Devam"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-uk/strings.xml b/Tethering/res/values-mcc204-mnc04-uk/strings.xml
new file mode 100644
index 0000000..dea3114
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-uk/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Точка доступу не підключена до Інтернету"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Не вдається підключити пристрої до Інтернету"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Вимкнути точку доступу"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Точку доступу ввімкнено"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"У роумінгу може стягуватися додаткова плата"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Продовжити"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-ur/strings.xml b/Tethering/res/values-mcc204-mnc04-ur/strings.xml
new file mode 100644
index 0000000..09bc0c9
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-ur/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"ہاٹ اسپاٹ میں انٹرنیٹ نہیں ہے"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"آلات انٹرنیٹ سے منسلک نہیں ہو سکتے"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"ہاٹ اسپاٹ آف کریں"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"ہاٹ اسپاٹ آن ہے"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"جاری رکھیں"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-uz/strings.xml b/Tethering/res/values-mcc204-mnc04-uz/strings.xml
new file mode 100644
index 0000000..715d348
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-uz/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Hotspot internetga ulanmagan"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Qurilmalar internetga ulana olmayapti"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Hotspotni faolsizlantirish"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Hotspot yoniq"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Rouming vaqtida qoʻshimcha haq olinishi mumkin"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Davom etish"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-vi/strings.xml b/Tethering/res/values-mcc204-mnc04-vi/strings.xml
new file mode 100644
index 0000000..bf4ee10
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-vi/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"Điểm phát sóng không có kết nối Internet"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Các thiết bị không thể kết nối Internet"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Tắt điểm phát sóng"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"Điểm phát sóng đang bật"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Bạn có thể mất thêm phí dữ liệu khi chuyển vùng"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Tiếp tục"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml
new file mode 100644
index 0000000..cdb4224
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rCN/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"热点没有网络连接"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"设备无法连接到互联网"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"关闭热点"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"热点已开启"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"漫游时可能会产生额外的费用"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"继续"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml
new file mode 100644
index 0000000..3bb52e4
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rHK/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"熱點沒有互聯網連線"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"裝置無法連線至互聯網"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"關閉熱點"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"已開啟熱點"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"漫遊時可能需要支付額外費用"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"繼續"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml b/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml
new file mode 100644
index 0000000..298c3ea
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zh-rTW/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"無線基地台沒有網際網路連線"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"裝置無法連上網際網路"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"關閉無線基地台"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"無線基地台已開啟"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"使用漫遊服務可能須支付額外費用"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"繼續"</string>
+</resources>
diff --git a/Tethering/res/values-mcc204-mnc04-zu/strings.xml b/Tethering/res/values-mcc204-mnc04-zu/strings.xml
new file mode 100644
index 0000000..3dc0078
--- /dev/null
+++ b/Tethering/res/values-mcc204-mnc04-zu/strings.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="no_upstream_notification_title" msgid="6246167638178412020">"I-Hotspot ayina-inthanethi"</string>
+ <string name="no_upstream_notification_message" msgid="5010177541603431003">"Amadivayisi awakwazi ukuxhuma ku-inthanethi"</string>
+ <string name="no_upstream_notification_disable_button" msgid="2613861474440640595">"Vala i-hotspot"</string>
+ <string name="upstream_roaming_notification_title" msgid="3633925855626231152">"I-Hotspot ivuliwe"</string>
+ <string name="upstream_roaming_notification_message" msgid="1396837704184358258">"Kungaba nezinkokhelo ezengeziwe uma uzula"</string>
+ <string name="upstream_roaming_notification_continue_button" msgid="5324117849715705638">"Qhubeka"</string>
+</resources>
diff --git a/Tethering/res/values/config.xml b/Tethering/res/values/config.xml
index 5f8d299..4391006 100644
--- a/Tethering/res/values/config.xml
+++ b/Tethering/res/values/config.xml
@@ -40,6 +40,8 @@
<string-array translatable="false" name="config_tether_wifi_regexs">
<item>"wlan\\d"</item>
<item>"softap\\d"</item>
+ <item>"ap_br_wlan\\d"</item>
+ <item>"ap_br_softap\\d"</item>
</string-array>
<!-- List of regexpressions describing the interface (if any) that represent tetherable
diff --git a/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java b/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
deleted file mode 100644
index b1ffdb0..0000000
--- a/Tethering/src/android/net/util/BaseNetdUnsolicitedEventListener.java
+++ /dev/null
@@ -1,75 +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 android.net.util;
-
-import android.net.INetdUnsolicitedEventListener;
-
-import androidx.annotation.NonNull;
-
-/**
- * Base {@link INetdUnsolicitedEventListener} that provides no-op implementations which can be
- * overridden.
- */
-public class BaseNetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub {
-
- @Override
- public void onInterfaceClassActivityChanged(boolean isActive, int timerLabel, long timestampNs,
- int uid) { }
-
- @Override
- public void onQuotaLimitReached(@NonNull String alertName, @NonNull String ifName) { }
-
- @Override
- public void onInterfaceDnsServerInfo(@NonNull String ifName, long lifetimeS,
- @NonNull String[] servers) { }
-
- @Override
- public void onInterfaceAddressUpdated(@NonNull String addr, String ifName, int flags,
- int scope) { }
-
- @Override
- public void onInterfaceAddressRemoved(@NonNull String addr, @NonNull String ifName, int flags,
- int scope) { }
-
- @Override
- public void onInterfaceAdded(@NonNull String ifName) { }
-
- @Override
- public void onInterfaceRemoved(@NonNull String ifName) { }
-
- @Override
- public void onInterfaceChanged(@NonNull String ifName, boolean up) { }
-
- @Override
- public void onInterfaceLinkStateChanged(@NonNull String ifName, boolean up) { }
-
- @Override
- public void onRouteChanged(boolean updated, @NonNull String route, @NonNull String gateway,
- @NonNull String ifName) { }
-
- @Override
- public void onStrictCleartextDetected(int uid, @NonNull String hex) { }
-
- @Override
- public int getInterfaceVersion() {
- return INetdUnsolicitedEventListener.VERSION;
- }
-
- @Override
- public String getInterfaceHash() {
- return INetdUnsolicitedEventListener.HASH;
- }
-}
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
index 20f30ea..d890e08 100644
--- a/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
+++ b/Tethering/src/com/android/networkstack/tethering/BpfCoordinator.java
@@ -24,6 +24,7 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
@@ -42,6 +43,7 @@
import android.os.Handler;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
@@ -51,6 +53,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
+import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.NetworkStackConstants;
+import com.android.networkstack.tethering.apishim.common.BpfCoordinatorShim;
import java.net.Inet6Address;
import java.util.ArrayList;
@@ -71,6 +76,8 @@
public class BpfCoordinator {
private static final String TAG = BpfCoordinator.class.getSimpleName();
private static final int DUMP_TIMEOUT_MS = 10_000;
+ private static final String TETHER_INGRESS_FS_PATH =
+ "/sys/fs/bpf/map_offload_tether_ingress_map";
@VisibleForTesting
enum StatsType {
@@ -88,6 +95,8 @@
private final Dependencies mDeps;
@Nullable
private final BpfTetherStatsProvider mStatsProvider;
+ @NonNull
+ private final BpfCoordinatorShim mBpfCoordinatorShim;
// True if BPF offload is supported, false otherwise. The BPF offload could be disabled by
// a runtime resource overlay package or device configuration. This flag is only initialized
@@ -168,6 +177,28 @@
/** Get tethering configuration. */
@Nullable public abstract TetheringConfiguration getTetherConfig();
+
+ /**
+ * Check OS Build at least S.
+ *
+ * TODO: move to BpfCoordinatorShim once the test doesn't need the mocked OS build for
+ * testing different code flows concurrently.
+ */
+ public boolean isAtLeastS() {
+ // TODO: consider using ShimUtils.isAtLeastS.
+ return SdkLevel.isAtLeastS();
+ }
+
+ /** Get ingress BPF map. */
+ @Nullable public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
+ try {
+ return new BpfMap<>(TETHER_INGRESS_FS_PATH,
+ BpfMap.BPF_F_RDWR, TetherIngressKey.class, TetherIngressValue.class);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot create ingress map: " + e);
+ return null;
+ }
+ }
}
@VisibleForTesting
@@ -188,6 +219,11 @@
provider = null;
}
mStatsProvider = provider;
+
+ mBpfCoordinatorShim = BpfCoordinatorShim.getBpfCoordinatorShim(deps);
+ if (!mBpfCoordinatorShim.isInitialized()) {
+ mLog.e("Bpf shim not initialized");
+ }
}
/**
@@ -199,8 +235,8 @@
public void startPolling() {
if (mPollingStarted) return;
- if (!mIsBpfEnabled) {
- mLog.i("Offload disabled");
+ if (!isUsingBpf()) {
+ mLog.i("BPF is not using");
return;
}
@@ -231,6 +267,10 @@
mLog.i("Polling stopped");
}
+ private boolean isUsingBpf() {
+ return mIsBpfEnabled && mBpfCoordinatorShim.isInitialized();
+ }
+
/**
* Add forwarding rule. After adding the first rule on a given upstream, must add the data
* limit on the given upstream.
@@ -238,15 +278,10 @@
*/
public void tetherOffloadRuleAdd(
@NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
- if (!mIsBpfEnabled) return;
+ if (!isUsingBpf()) return;
- try {
- // TODO: Perhaps avoid to add a duplicate rule.
- mNetd.tetherOffloadRuleAdd(rule.toTetherOffloadRuleParcel());
- } catch (RemoteException | ServiceSpecificException e) {
- mLog.e("Could not add IPv6 forwarding rule: ", e);
- return;
- }
+ // TODO: Perhaps avoid to add a duplicate rule.
+ if (!mBpfCoordinatorShim.tetherOffloadRuleAdd(rule)) return;
if (!mIpv6ForwardingRules.containsKey(ipServer)) {
mIpv6ForwardingRules.put(ipServer, new LinkedHashMap<Inet6Address,
@@ -279,7 +314,7 @@
*/
public void tetherOffloadRuleRemove(
@NonNull final IpServer ipServer, @NonNull final Ipv6ForwardingRule rule) {
- if (!mIsBpfEnabled) return;
+ if (!isUsingBpf()) return;
try {
// TODO: Perhaps avoid to remove a non-existent rule.
@@ -324,7 +359,7 @@
* Note that this can be only called on handler thread.
*/
public void tetherOffloadRuleClear(@NonNull final IpServer ipServer) {
- if (!mIsBpfEnabled) return;
+ if (!isUsingBpf()) return;
final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
ipServer);
@@ -341,7 +376,7 @@
* Note that this can be only called on handler thread.
*/
public void tetherOffloadRuleUpdate(@NonNull final IpServer ipServer, int newUpstreamIfindex) {
- if (!mIsBpfEnabled) return;
+ if (!isUsingBpf()) return;
final LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules = mIpv6ForwardingRules.get(
ipServer);
@@ -365,7 +400,7 @@
* Note that this can be only called on handler thread.
*/
public void addUpstreamNameToLookupTable(int upstreamIfindex, @NonNull String upstreamIface) {
- if (!mIsBpfEnabled) return;
+ if (!isUsingBpf()) return;
if (upstreamIfindex == 0 || TextUtils.isEmpty(upstreamIface)) return;
@@ -396,6 +431,7 @@
? "registered" : "not registered"));
pw.println("Upstream quota: " + mInterfaceQuotas.toString());
pw.println("Polling interval: " + getPollingInterval() + " ms");
+ pw.println("Bpf shim: " + mBpfCoordinatorShim.toString());
pw.println("Forwarding stats:");
pw.increaseIndent();
@@ -497,6 +533,23 @@
return parcel;
}
+ /**
+ * Return a TetherIngressKey object built from the rule.
+ */
+ @NonNull
+ public TetherIngressKey makeTetherIngressKey() {
+ return new TetherIngressKey(upstreamIfindex, address.getAddress());
+ }
+
+ /**
+ * Return a TetherIngressValue object built from the rule.
+ */
+ @NonNull
+ public TetherIngressValue makeTetherIngressValue() {
+ return new TetherIngressValue(downstreamIfindex, dstMac, srcMac, ETH_P_IPV6,
+ NetworkStackConstants.ETHER_MTU);
+ }
+
@Override
public boolean equals(Object o) {
if (!(o instanceof Ipv6ForwardingRule)) return false;
diff --git a/Tethering/src/com/android/networkstack/tethering/BpfMap.java b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
new file mode 100644
index 0000000..69ad1b6
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/BpfMap.java
@@ -0,0 +1,219 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.system.OsConstants.EEXIST;
+import static android.system.OsConstants.ENOENT;
+
+import android.system.ErrnoException;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.net.module.util.Struct;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+
+/**
+ * BpfMap is a key -> value mapping structure that is designed to maintained the bpf map entries.
+ * This is a wrapper class of in-kernel data structure. The in-kernel data can be read/written by
+ * passing syscalls with map file descriptor.
+ *
+ * @param <K> the key of the map.
+ * @param <V> the value of the map.
+ */
+public class BpfMap<K extends Struct, V extends Struct> implements AutoCloseable {
+ // Following definitions from kernel include/uapi/linux/bpf.h
+ public static final int BPF_F_RDWR = 0;
+ public static final int BPF_F_RDONLY = 1 << 3;
+ public static final int BPF_F_WRONLY = 1 << 4;
+
+ public static final int BPF_MAP_TYPE_HASH = 1;
+
+ private static final int BPF_F_NO_PREALLOC = 1;
+
+ private static final int BPF_ANY = 0;
+ private static final int BPF_NOEXIST = 1;
+ private static final int BPF_EXIST = 2;
+
+ private final int mMapFd;
+ private final Class<K> mKeyClass;
+ private final Class<V> mValueClass;
+ private final int mKeySize;
+ private final int mValueSize;
+
+ /**
+ * Create a BpfMap map wrapper with "path" of filesystem.
+ *
+ * @param flag the access mode, one of BPF_F_RDWR, BPF_F_RDONLY, or BPF_F_WRONLY.
+ * @throws ErrnoException if the BPF map associated with {@code path} cannot be retrieved.
+ * @throws NullPointerException if {@code path} is null.
+ */
+ public BpfMap(@NonNull final String path, final int flag, final Class<K> key,
+ final Class<V> value) throws ErrnoException, NullPointerException {
+ mMapFd = bpfFdGet(path, flag);
+
+ mKeyClass = key;
+ mValueClass = value;
+ mKeySize = Struct.getSize(key);
+ mValueSize = Struct.getSize(value);
+ }
+
+ /**
+ * Update an existing or create a new key -> value entry in an eBbpf map.
+ */
+ public void updateEntry(K key, V value) throws ErrnoException {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_ANY);
+ }
+
+ /**
+ * If the key does not exist in the map, insert key -> value entry into eBpf map.
+ * Otherwise IllegalStateException will be thrown.
+ */
+ public void insertEntry(K key, V value)
+ throws ErrnoException, IllegalStateException {
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_NOEXIST);
+ } catch (ErrnoException e) {
+ if (e.errno == EEXIST) throw new IllegalStateException(key + " already exists");
+
+ throw e;
+ }
+ }
+
+ /**
+ * If the key already exists in the map, replace its value. Otherwise NoSuchElementException
+ * will be thrown.
+ */
+ public void replaceEntry(K key, V value)
+ throws ErrnoException, NoSuchElementException {
+ try {
+ writeToMapEntry(mMapFd, key.writeToBytes(), value.writeToBytes(), BPF_EXIST);
+ } catch (ErrnoException e) {
+ if (e.errno == ENOENT) throw new NoSuchElementException(key + " not found");
+
+ throw e;
+ }
+ }
+
+ /** Remove existing key from eBpf map. Return false if map was not modified. */
+ public boolean deleteEntry(K key) throws ErrnoException {
+ return deleteMapEntry(mMapFd, key.writeToBytes());
+ }
+
+ private K getNextKeyInternal(@Nullable K key) throws ErrnoException {
+ final byte[] rawKey = getNextRawKey(
+ key == null ? null : key.writeToBytes());
+ if (rawKey == null) return null;
+
+ final ByteBuffer buffer = ByteBuffer.wrap(rawKey);
+ buffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(mKeyClass, buffer);
+ }
+
+ /**
+ * Get the next key of the passed-in key. If the passed-in key is not found, return the first
+ * key. If the passed-in key is the last one, return null.
+ *
+ * TODO: consider allowing null passed-in key.
+ */
+ public K getNextKey(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+ return getNextKeyInternal(key);
+ }
+
+ private byte[] getNextRawKey(@Nullable final byte[] key) throws ErrnoException {
+ byte[] nextKey = new byte[mKeySize];
+ if (getNextMapKey(mMapFd, key, nextKey)) return nextKey;
+
+ return null;
+ }
+
+ /** Get the first key of eBpf map. */
+ public K getFirstKey() throws ErrnoException {
+ return getNextKeyInternal(null);
+ }
+
+ /** Check whether a key exists in the map. */
+ public boolean containsKey(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+
+ final byte[] rawValue = getRawValue(key.writeToBytes());
+ return rawValue != null;
+ }
+
+ /** Retrieve a value from the map. Return null if there is no such key. */
+ public V getValue(@NonNull K key) throws ErrnoException {
+ Objects.requireNonNull(key);
+ final byte[] rawValue = getRawValue(key.writeToBytes());
+
+ if (rawValue == null) return null;
+
+ final ByteBuffer buffer = ByteBuffer.wrap(rawValue);
+ buffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(mValueClass, buffer);
+ }
+
+ private byte[] getRawValue(final byte[] key) throws ErrnoException {
+ byte[] value = new byte[mValueSize];
+ if (findMapEntry(mMapFd, key, value)) return value;
+
+ return null;
+ }
+
+ /**
+ * Iterate through the map and handle each key -> value retrieved base on the given BiConsumer.
+ * The given BiConsumer may to delete the passed-in entry, but is not allowed to perform any
+ * other structural modifications to the map, such as adding entries or deleting other entries.
+ * Otherwise, iteration will result in undefined behaviour.
+ */
+ public void forEach(BiConsumer<K, V> action) throws ErrnoException {
+ @Nullable K nextKey = getFirstKey();
+
+ while (nextKey != null) {
+ @NonNull final K curKey = nextKey;
+ @NonNull final V value = getValue(curKey);
+
+ nextKey = getNextKey(curKey);
+ action.accept(curKey, value);
+ }
+ }
+
+ @Override
+ public void close() throws Exception {
+ closeMap(mMapFd);
+ }
+
+ private static native int closeMap(int fd) throws ErrnoException;
+
+ private native int bpfFdGet(String path, int mode) throws ErrnoException, NullPointerException;
+
+ private native void writeToMapEntry(int fd, byte[] key, byte[] value, int flags)
+ throws ErrnoException;
+
+ private native boolean deleteMapEntry(int fd, byte[] key) throws ErrnoException;
+
+ // If key is found, the operation returns true and the nextKey would reference to the next
+ // element. If key is not found, the operation returns true and the nextKey would reference to
+ // the first element. If key is the last element, false is returned.
+ private native boolean getNextMapKey(int fd, byte[] key, byte[] nextKey) throws ErrnoException;
+
+ private native boolean findMapEntry(int fd, byte[] key, byte[] value) throws ErrnoException;
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java b/Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java
new file mode 100644
index 0000000..78683c5
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/TetherIngressKey.java
@@ -0,0 +1,72 @@
+/*
+ * 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.networkstack.tethering;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Arrays;
+
+/** The key of BpfMap which is used for bpf offload. */
+public class TetherIngressKey extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long iif; // The input interface index.
+
+ @Field(order = 1, type = Type.ByteArray, arraysize = 16)
+ public final byte[] neigh6; // The destination IPv6 address.
+
+ public TetherIngressKey(final long iif, final byte[] neigh6) {
+ try {
+ final Inet6Address unused = (Inet6Address) InetAddress.getByAddress(neigh6);
+ } catch (ClassCastException | UnknownHostException e) {
+ throw new IllegalArgumentException("Invalid IPv6 address: "
+ + Arrays.toString(neigh6));
+ }
+ this.iif = iif;
+ this.neigh6 = neigh6;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof TetherIngressKey)) return false;
+
+ final TetherIngressKey that = (TetherIngressKey) obj;
+
+ return iif == that.iif && Arrays.equals(neigh6, that.neigh6);
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(iif) ^ Arrays.hashCode(neigh6);
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return String.format("iif: %d, neigh: %s", iif, Inet6Address.getByAddress(neigh6));
+ } catch (UnknownHostException e) {
+ // Should not happen because construtor already verify neigh6.
+ throw new IllegalStateException("Invalid TetherIngressKey");
+ }
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java b/Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java
new file mode 100644
index 0000000..e2116fc
--- /dev/null
+++ b/Tethering/src/com/android/networkstack/tethering/TetherIngressValue.java
@@ -0,0 +1,80 @@
+/*
+ * 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.networkstack.tethering;
+
+import android.net.MacAddress;
+
+import androidx.annotation.NonNull;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.util.Objects;
+
+/** The value of BpfMap which is used for bpf offload. */
+public class TetherIngressValue extends Struct {
+ @Field(order = 0, type = Type.U32)
+ public final long oif; // The output interface index.
+
+ // The ethhdr struct which is defined in uapi/linux/if_ether.h
+ @Field(order = 1, type = Type.EUI48)
+ public final MacAddress ethDstMac; // The destination mac address.
+ @Field(order = 2, type = Type.EUI48)
+ public final MacAddress ethSrcMac; // The source mac address.
+ @Field(order = 3, type = Type.UBE16)
+ public final int ethProto; // Packet type ID field.
+
+ @Field(order = 4, type = Type.U16)
+ public final int pmtu; // The maximum L3 output path/route mtu.
+
+ public TetherIngressValue(final long oif, @NonNull final MacAddress ethDstMac,
+ @NonNull final MacAddress ethSrcMac, final int ethProto, final int pmtu) {
+ Objects.requireNonNull(ethSrcMac);
+ Objects.requireNonNull(ethDstMac);
+
+ this.oif = oif;
+ this.ethDstMac = ethDstMac;
+ this.ethSrcMac = ethSrcMac;
+ this.ethProto = ethProto;
+ this.pmtu = pmtu;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) return true;
+
+ if (!(obj instanceof TetherIngressValue)) return false;
+
+ final TetherIngressValue that = (TetherIngressValue) obj;
+
+ return oif == that.oif && ethDstMac.equals(that.ethDstMac)
+ && ethSrcMac.equals(that.ethSrcMac) && ethProto == that.ethProto
+ && pmtu == that.pmtu;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(oif, ethDstMac, ethSrcMac, ethProto, pmtu);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("oif: %d, dstMac: %s, srcMac: %s, proto: %d, pmtu: %d", oif,
+ ethDstMac, ethSrcMac, ethProto, pmtu);
+ }
+}
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index 62ae88c..fdd1c40 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -93,7 +93,6 @@
import android.net.TetheringRequestParcel;
import android.net.ip.IpServer;
import android.net.shared.NetdUtils;
-import android.net.util.BaseNetdUnsolicitedEventListener;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
@@ -132,6 +131,7 @@
import com.android.internal.util.MessageUtils;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
+import com.android.net.module.util.BaseNetdUnsolicitedEventListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index d084ca0..d637ba7 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -102,8 +102,9 @@
}
@Override
- public void tether(String iface, String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void tether(String iface, String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
listener.onResult(mTethering.tether(iface));
@@ -111,8 +112,9 @@
}
@Override
- public void untether(String iface, String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void untether(String iface, String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
listener.onResult(mTethering.untether(iface));
@@ -120,8 +122,9 @@
}
@Override
- public void setUsbTethering(boolean enable, String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void setUsbTethering(boolean enable, String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
listener.onResult(mTethering.setUsbTethering(enable));
@@ -130,8 +133,9 @@
@Override
public void startTethering(TetheringRequestParcel request, String callerPkg,
- IIntResultListener listener) {
+ String callingAttributionTag, IIntResultListener listener) {
if (checkAndNotifyCommonError(callerPkg,
+ callingAttributionTag,
request.exemptFromEntitlementCheck /* onlyAllowPrivileged */,
listener)) {
return;
@@ -141,8 +145,9 @@
}
@Override
- public void stopTethering(int type, String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void stopTethering(int type, String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
mTethering.stopTethering(type);
@@ -152,8 +157,8 @@
@Override
public void requestLatestTetheringEntitlementResult(int type, ResultReceiver receiver,
- boolean showEntitlementUi, String callerPkg) {
- if (checkAndNotifyCommonError(callerPkg, receiver)) return;
+ boolean showEntitlementUi, String callerPkg, String callingAttributionTag) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, receiver)) return;
mTethering.requestLatestTetheringEntitlementResult(type, receiver, showEntitlementUi);
}
@@ -183,8 +188,9 @@
}
@Override
- public void stopAllTethering(String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void stopAllTethering(String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
mTethering.untetherAll();
@@ -193,8 +199,9 @@
}
@Override
- public void isTetheringSupported(String callerPkg, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, listener)) return;
+ public void isTetheringSupported(String callerPkg, String callingAttributionTag,
+ IIntResultListener listener) {
+ if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
try {
listener.onResult(TETHER_ERROR_NO_ERROR);
@@ -207,14 +214,18 @@
mTethering.dump(fd, writer, args);
}
- private boolean checkAndNotifyCommonError(String callerPkg, IIntResultListener listener) {
- return checkAndNotifyCommonError(callerPkg, false /* onlyAllowPrivileged */, listener);
+ private boolean checkAndNotifyCommonError(final String callerPkg,
+ final String callingAttributionTag, final IIntResultListener listener) {
+ return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, listener);
}
private boolean checkAndNotifyCommonError(final String callerPkg,
- final boolean onlyAllowPrivileged, final IIntResultListener listener) {
+ final String callingAttributionTag, final boolean onlyAllowPrivileged,
+ final IIntResultListener listener) {
try {
- if (!hasTetherChangePermission(callerPkg, onlyAllowPrivileged)) {
+ if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
+ onlyAllowPrivileged)) {
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
@@ -229,8 +240,10 @@
return false;
}
- private boolean checkAndNotifyCommonError(String callerPkg, ResultReceiver receiver) {
- if (!hasTetherChangePermission(callerPkg, false /* onlyAllowPrivileged */)) {
+ private boolean checkAndNotifyCommonError(final String callerPkg,
+ final String callingAttributionTag, final ResultReceiver receiver) {
+ if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */)) {
receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
return true;
}
@@ -256,7 +269,7 @@
}
private boolean hasTetherChangePermission(final String callerPkg,
- final boolean onlyAllowPrivileged) {
+ final String callingAttributionTag, final boolean onlyAllowPrivileged) {
if (onlyAllowPrivileged && !hasNetworkStackPermission()) return false;
if (hasTetherPrivilegedPermission()) return true;
@@ -264,11 +277,12 @@
if (mTethering.isTetherProvisioningRequired()) return false;
int uid = Binder.getCallingUid();
+
// If callerPkg's uid is not same as Binder.getCallingUid(),
// checkAndNoteWriteSettingsOperation will return false and the operation will be
// denied.
return mService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg,
- false /* throwException */);
+ callingAttributionTag, false /* throwException */);
}
private boolean hasTetherAccessPermission() {
@@ -287,9 +301,10 @@
*/
@VisibleForTesting
boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
- @NonNull String callingPackage, boolean throwException) {
+ @NonNull String callingPackage, @Nullable String callingAttributionTag,
+ boolean throwException) {
return Settings.checkAndNoteWriteSettingsOperation(context, uid, callingPackage,
- throwException);
+ callingAttributionTag, throwException);
}
/**
diff --git a/Tethering/tests/integration/Android.bp b/Tethering/tests/integration/Android.bp
index db503c6..42f83bf 100644
--- a/Tethering/tests/integration/Android.bp
+++ b/Tethering/tests/integration/Android.bp
@@ -45,7 +45,7 @@
defaults: ["TetheringIntegrationTestsDefaults"],
visibility: [
"//cts/tests/tests/tethering",
- "//packages/modules/Connectivity/tests/cts/tethering"
+ "//packages/modules/Connectivity/tests/cts/tethering",
]
}
diff --git a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
index 747d3e8..42a91aa 100644
--- a/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
+++ b/Tethering/tests/privileged/src/android/net/ip/DadProxyTest.java
@@ -17,9 +17,9 @@
package android.net.ip;
import static android.system.OsConstants.IPPROTO_ICMPV6;
-import static android.system.OsConstants.IPPROTO_TCP;
-import static com.android.internal.util.BitUtils.uint16;
+import static com.android.net.module.util.IpUtils.icmpv6Checksum;
+import static com.android.net.module.util.NetworkStackConstants.ETHER_SRC_ADDR_OFFSET;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -30,34 +30,29 @@
import android.net.INetd;
import android.net.InetAddresses;
import android.net.MacAddress;
-import android.net.TestNetworkInterface;
-import android.net.TestNetworkManager;
import android.net.util.InterfaceParams;
-import android.net.util.IpUtils;
import android.net.util.TetheringUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
-import android.system.ErrnoException;
-import android.system.Os;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.testutils.TapPacketReader;
+import com.android.testutils.TapPacketReaderRule;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
-import java.io.FileDescriptor;
import java.nio.ByteBuffer;
-import java.util.concurrent.atomic.AtomicReference;
@RunWith(AndroidJUnit4.class)
@SmallTest
@@ -65,16 +60,18 @@
private static final int DATA_BUFFER_LEN = 4096;
private static final int PACKET_TIMEOUT_MS = 5_000;
- // TODO: make NetworkStackConstants accessible to this test and use the constant from there.
- private static final int ETHER_SRC_ADDR_OFFSET = 6;
+ // Start the readers manually on a common handler shared with DadProxy, for simplicity
+ @Rule
+ public final TapPacketReaderRule mUpstreamReader = new TapPacketReaderRule(
+ DATA_BUFFER_LEN, false /* autoStart */);
+ @Rule
+ public final TapPacketReaderRule mTetheredReader = new TapPacketReaderRule(
+ DATA_BUFFER_LEN, false /* autoStart */);
- private DadProxy mProxy;
- TestNetworkInterface mUpstreamTestIface, mTetheredTestIface;
private InterfaceParams mUpstreamParams, mTetheredParams;
private HandlerThread mHandlerThread;
private Handler mHandler;
private TapPacketReader mUpstreamPacketReader, mTetheredPacketReader;
- private FileDescriptor mUpstreamTapFd, mTetheredTapFd;
private static INetd sNetd;
@@ -106,12 +103,12 @@
@After
public void tearDown() throws Exception {
+ mUpstreamReader.stop();
+ mTetheredReader.stop();
+
if (mHandlerThread != null) {
- mHandler.post(mUpstreamPacketReader::stop); // Also closes the socket
- mHandler.post(mTetheredPacketReader::stop); // Also closes the socket
- mUpstreamTapFd = null;
- mTetheredTapFd = null;
mHandlerThread.quitSafely();
+ mHandlerThread.join(PACKET_TIMEOUT_MS);
}
if (mTetheredParams != null) {
@@ -120,54 +117,20 @@
if (mUpstreamParams != null) {
sNetd.networkRemoveInterface(INetd.LOCAL_NET_ID, mUpstreamParams.name);
}
-
- if (mUpstreamTestIface != null) {
- try {
- Os.close(mUpstreamTestIface.getFileDescriptor().getFileDescriptor());
- } catch (ErrnoException e) { }
- }
-
- if (mTetheredTestIface != null) {
- try {
- Os.close(mTetheredTestIface.getFileDescriptor().getFileDescriptor());
- } catch (ErrnoException e) { }
- }
- }
-
- private TestNetworkInterface setupTapInterface() {
- final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
- AtomicReference<TestNetworkInterface> iface = new AtomicReference<>();
-
- inst.getUiAutomation().adoptShellPermissionIdentity();
- try {
- final TestNetworkManager tnm = (TestNetworkManager) inst.getContext().getSystemService(
- Context.TEST_NETWORK_SERVICE);
- iface.set(tnm.createTapInterface());
- } finally {
- inst.getUiAutomation().dropShellPermissionIdentity();
- }
-
- return iface.get();
}
private void setupTapInterfaces() {
// Create upstream test iface.
- mUpstreamTestIface = setupTapInterface();
- mUpstreamParams = InterfaceParams.getByName(mUpstreamTestIface.getInterfaceName());
+ mUpstreamReader.start(mHandler);
+ mUpstreamParams = InterfaceParams.getByName(mUpstreamReader.iface.getInterfaceName());
assertNotNull(mUpstreamParams);
- mUpstreamTapFd = mUpstreamTestIface.getFileDescriptor().getFileDescriptor();
- mUpstreamPacketReader = new TapPacketReader(mHandler, mUpstreamTapFd,
- DATA_BUFFER_LEN);
- mHandler.post(mUpstreamPacketReader::start);
+ mUpstreamPacketReader = mUpstreamReader.getReader();
// Create tethered test iface.
- mTetheredTestIface = setupTapInterface();
- mTetheredParams = InterfaceParams.getByName(mTetheredTestIface.getInterfaceName());
+ mTetheredReader.start(mHandler);
+ mTetheredParams = InterfaceParams.getByName(mTetheredReader.getIface().getInterfaceName());
assertNotNull(mTetheredParams);
- mTetheredTapFd = mTetheredTestIface.getFileDescriptor().getFileDescriptor();
- mTetheredPacketReader = new TapPacketReader(mHandler, mTetheredTapFd,
- DATA_BUFFER_LEN);
- mHandler.post(mTetheredPacketReader::start);
+ mTetheredPacketReader = mTetheredReader.getReader();
}
private static final int IPV6_HEADER_LEN = 40;
@@ -177,31 +140,6 @@
private static final int ICMPV6_CHECKSUM_OFFSET = 2;
private static final int ETHER_TYPE_IPV6 = 0x86dd;
- // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
- private static int checksumFold(int sum) {
- while (sum > 0xffff) {
- sum = (sum >> 16) + (sum & 0xffff);
- }
- return sum;
- }
-
- // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
- private static short checksumAdjust(short checksum, short oldWord, short newWord) {
- checksum = (short) ~checksum;
- int tempSum = checksumFold(uint16(checksum) + uint16(newWord) + 0xffff - uint16(oldWord));
- return (short) ~tempSum;
- }
-
- // TODO: move the IpUtils code to frameworks/lib/net and link it statically.
- private static short icmpv6Checksum(ByteBuffer buf, int ipOffset, int transportOffset,
- int transportLen) {
- // The ICMPv6 checksum is the same as the TCP checksum, except the pseudo-header uses
- // 58 (ICMPv6) instead of 6 (TCP). Calculate the TCP checksum, and then do an incremental
- // checksum adjustment for the change in the next header byte.
- short checksum = IpUtils.tcpChecksum(buf, ipOffset, transportOffset, transportLen);
- return checksumAdjust(checksum, (short) IPPROTO_TCP, (short) IPPROTO_ICMPV6);
- }
-
private static ByteBuffer createDadPacket(int type) {
// Refer to buildArpPacket()
int icmpLen = ICMPV6_NA_NS_LEN
diff --git a/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
new file mode 100644
index 0000000..1ddbaa9
--- /dev/null
+++ b/Tethering/tests/privileged/src/com/android/networkstack/tethering/BpfMapTest.java
@@ -0,0 +1,354 @@
+/*
+ * 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.networkstack.tethering;
+
+import static android.system.OsConstants.ETH_P_IPV6;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.net.MacAddress;
+import android.os.Build;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+@RunWith(AndroidJUnit4.class)
+@IgnoreUpTo(Build.VERSION_CODES.R)
+public final class BpfMapTest {
+ // Sync from packages/modules/Connectivity/Tethering/bpf_progs/offload.c.
+ private static final int TEST_MAP_SIZE = 16;
+ private static final String TETHER_INGRESS_FS_PATH =
+ "/sys/fs/bpf/map_offload_tether_ingress_map_TEST";
+
+ private ArrayMap<TetherIngressKey, TetherIngressValue> mTestData;
+
+ @BeforeClass
+ public static void setupOnce() {
+ System.loadLibrary("tetherutilsjni");
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ // TODO: Simply the test map creation and deletion.
+ // - Make the map a class member (mTestMap)
+ // - Open the test map RW in setUp
+ // - Close the test map in tearDown.
+ cleanTestMap();
+
+ mTestData = new ArrayMap<>();
+ mTestData.put(createTetherIngressKey(101, "2001:db8::1"),
+ createTetherIngressValue(11, "00:00:00:00:00:0a", "11:11:11:00:00:0b", ETH_P_IPV6,
+ 1280));
+ mTestData.put(createTetherIngressKey(102, "2001:db8::2"),
+ createTetherIngressValue(22, "00:00:00:00:00:0c", "22:22:22:00:00:0d", ETH_P_IPV6,
+ 1400));
+ mTestData.put(createTetherIngressKey(103, "2001:db8::3"),
+ createTetherIngressValue(33, "00:00:00:00:00:0e", "33:33:33:00:00:0f", ETH_P_IPV6,
+ 1500));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ cleanTestMap();
+ }
+
+ private BpfMap<TetherIngressKey, TetherIngressValue> getTestMap() throws Exception {
+ return new BpfMap<>(
+ TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherIngressKey.class, TetherIngressValue.class);
+ }
+
+ private void cleanTestMap() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ bpfMap.forEach((key, value) -> {
+ try {
+ assertTrue(bpfMap.deleteEntry(key));
+ } catch (ErrnoException e) {
+ fail("Fail to delete the key " + key + ": " + e);
+ }
+ });
+ assertNull(bpfMap.getFirstKey());
+ }
+ }
+
+ private TetherIngressKey createTetherIngressKey(long iif, String address) throws Exception {
+ final InetAddress ipv6Address = InetAddress.getByName(address);
+
+ return new TetherIngressKey(iif, ipv6Address.getAddress());
+ }
+
+ private TetherIngressValue createTetherIngressValue(long oif, String src, String dst, int proto,
+ int pmtu) throws Exception {
+ final MacAddress srcMac = MacAddress.fromString(src);
+ final MacAddress dstMac = MacAddress.fromString(dst);
+
+ return new TetherIngressValue(oif, dstMac, srcMac, proto, pmtu);
+ }
+
+ @Test
+ public void testGetFd() throws Exception {
+ try (BpfMap readOnlyMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDONLY,
+ TetherIngressKey.class, TetherIngressValue.class)) {
+ assertNotNull(readOnlyMap);
+ try {
+ readOnlyMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ fail("Writing RO map should throw ErrnoException");
+ } catch (ErrnoException expected) {
+ assertEquals(OsConstants.EPERM, expected.errno);
+ }
+ }
+ try (BpfMap writeOnlyMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_WRONLY,
+ TetherIngressKey.class, TetherIngressValue.class)) {
+ assertNotNull(writeOnlyMap);
+ try {
+ writeOnlyMap.getFirstKey();
+ fail("Reading WO map should throw ErrnoException");
+ } catch (ErrnoException expected) {
+ assertEquals(OsConstants.EPERM, expected.errno);
+ }
+ }
+ try (BpfMap readWriteMap = new BpfMap<>(TETHER_INGRESS_FS_PATH, BpfMap.BPF_F_RDWR,
+ TetherIngressKey.class, TetherIngressValue.class)) {
+ assertNotNull(readWriteMap);
+ }
+ }
+
+ @Test
+ public void testGetFirstKey() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ // getFirstKey on an empty map returns null.
+ assertFalse(bpfMap.containsKey(mTestData.keyAt(0)));
+ assertNull(bpfMap.getFirstKey());
+ assertNull(bpfMap.getValue(mTestData.keyAt(0)));
+
+ // getFirstKey on a non-empty map returns the first key.
+ bpfMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ assertEquals(mTestData.keyAt(0), bpfMap.getFirstKey());
+ }
+ }
+
+ @Test
+ public void testGetNextKey() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ // [1] If the passed-in key is not found on empty map, return null.
+ final TetherIngressKey nonexistentKey = createTetherIngressKey(1234, "2001:db8::10");
+ assertNull(bpfMap.getNextKey(nonexistentKey));
+
+ // [2] If the passed-in key is null on empty map, throw NullPointerException.
+ try {
+ bpfMap.getNextKey(null);
+ fail("Getting next key with null key should throw NullPointerException");
+ } catch (NullPointerException expected) { }
+
+ // The BPF map has one entry now.
+ final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap = new ArrayMap<>();
+ bpfMap.insertEntry(mTestData.keyAt(0), mTestData.valueAt(0));
+ resultMap.put(mTestData.keyAt(0), mTestData.valueAt(0));
+
+ // [3] If the passed-in key is the last key, return null.
+ // Because there is only one entry in the map, the first key equals the last key.
+ final TetherIngressKey lastKey = bpfMap.getFirstKey();
+ assertNull(bpfMap.getNextKey(lastKey));
+
+ // The BPF map has two entries now.
+ bpfMap.insertEntry(mTestData.keyAt(1), mTestData.valueAt(1));
+ resultMap.put(mTestData.keyAt(1), mTestData.valueAt(1));
+
+ // [4] If the passed-in key is found, return the next key.
+ TetherIngressKey nextKey = bpfMap.getFirstKey();
+ while (nextKey != null) {
+ if (resultMap.remove(nextKey).equals(nextKey)) {
+ fail("Unexpected result: " + nextKey);
+ }
+ nextKey = bpfMap.getNextKey(nextKey);
+ }
+ assertTrue(resultMap.isEmpty());
+
+ // [5] If the passed-in key is not found on non-empty map, return the first key.
+ assertEquals(bpfMap.getFirstKey(), bpfMap.getNextKey(nonexistentKey));
+
+ // [6] If the passed-in key is null on non-empty map, throw NullPointerException.
+ try {
+ bpfMap.getNextKey(null);
+ fail("Getting next key with null key should throw NullPointerException");
+ } catch (NullPointerException expected) { }
+ }
+ }
+
+ @Test
+ public void testUpdateBpfMap() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+
+ final TetherIngressKey key = mTestData.keyAt(0);
+ final TetherIngressValue value = mTestData.valueAt(0);
+ final TetherIngressValue value2 = mTestData.valueAt(1);
+ assertFalse(bpfMap.deleteEntry(key));
+
+ // updateEntry will create an entry if it does not exist already.
+ bpfMap.updateEntry(key, value);
+ assertTrue(bpfMap.containsKey(key));
+ final TetherIngressValue result = bpfMap.getValue(key);
+ assertEquals(value, result);
+
+ // updateEntry will update an entry that already exists.
+ bpfMap.updateEntry(key, value2);
+ assertTrue(bpfMap.containsKey(key));
+ final TetherIngressValue result2 = bpfMap.getValue(key);
+ assertEquals(value2, result2);
+
+ assertTrue(bpfMap.deleteEntry(key));
+ assertFalse(bpfMap.containsKey(key));
+ }
+ }
+
+ @Test
+ public void testInsertReplaceEntry() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+
+ final TetherIngressKey key = mTestData.keyAt(0);
+ final TetherIngressValue value = mTestData.valueAt(0);
+ final TetherIngressValue value2 = mTestData.valueAt(1);
+
+ try {
+ bpfMap.replaceEntry(key, value);
+ fail("Replacing non-existent key " + key + " should throw NoSuchElementException");
+ } catch (NoSuchElementException expected) { }
+ assertFalse(bpfMap.containsKey(key));
+
+ bpfMap.insertEntry(key, value);
+ assertTrue(bpfMap.containsKey(key));
+ final TetherIngressValue result = bpfMap.getValue(key);
+ assertEquals(value, result);
+ try {
+ bpfMap.insertEntry(key, value);
+ fail("Inserting existing key " + key + " should throw IllegalStateException");
+ } catch (IllegalStateException expected) { }
+
+ bpfMap.replaceEntry(key, value2);
+ assertTrue(bpfMap.containsKey(key));
+ final TetherIngressValue result2 = bpfMap.getValue(key);
+ assertEquals(value2, result2);
+ }
+ }
+
+ @Test
+ public void testIterateBpfMap() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap =
+ new ArrayMap<>(mTestData);
+
+ for (int i = 0; i < resultMap.size(); i++) {
+ bpfMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
+ }
+
+ bpfMap.forEach((key, value) -> {
+ if (!value.equals(resultMap.remove(key))) {
+ fail("Unexpected result: " + key + ", value: " + value);
+ }
+ });
+ assertTrue(resultMap.isEmpty());
+ }
+ }
+
+ @Test
+ public void testIterateEmptyMap() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ // Can't use an int because variables used in a lambda must be final.
+ final AtomicInteger count = new AtomicInteger();
+ bpfMap.forEach((key, value) -> count.incrementAndGet());
+ // Expect that the consumer was never called.
+ assertEquals(0, count.get());
+ }
+ }
+
+ @Test
+ public void testIterateDeletion() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ final ArrayMap<TetherIngressKey, TetherIngressValue> resultMap =
+ new ArrayMap<>(mTestData);
+
+ for (int i = 0; i < resultMap.size(); i++) {
+ bpfMap.insertEntry(resultMap.keyAt(i), resultMap.valueAt(i));
+ }
+
+ // Can't use an int because variables used in a lambda must be final.
+ final AtomicInteger count = new AtomicInteger();
+ bpfMap.forEach((key, value) -> {
+ try {
+ assertTrue(bpfMap.deleteEntry(key));
+ } catch (ErrnoException e) {
+ fail("Fail to delete key " + key + ": " + e);
+ }
+ if (!value.equals(resultMap.remove(key))) {
+ fail("Unexpected result: " + key + ", value: " + value);
+ }
+ count.incrementAndGet();
+ });
+ assertEquals(3, count.get());
+ assertTrue(resultMap.isEmpty());
+ assertNull(bpfMap.getFirstKey());
+ }
+ }
+
+ @Test
+ public void testInsertOverflow() throws Exception {
+ try (BpfMap<TetherIngressKey, TetherIngressValue> bpfMap = getTestMap()) {
+ final ArrayMap<TetherIngressKey, TetherIngressValue> testData = new ArrayMap<>();
+
+ // Build test data for TEST_MAP_SIZE + 1 entries.
+ for (int i = 1; i <= TEST_MAP_SIZE + 1; i++) {
+ testData.put(createTetherIngressKey(i, "2001:db8::1"), createTetherIngressValue(
+ 100, "de:ad:be:ef:00:01", "de:ad:be:ef:00:02", ETH_P_IPV6, 1500));
+ }
+
+ // Insert #TEST_MAP_SIZE test entries to the map. The map has reached the limit.
+ for (int i = 0; i < TEST_MAP_SIZE; i++) {
+ bpfMap.insertEntry(testData.keyAt(i), testData.valueAt(i));
+ }
+
+ // The map won't allow inserting any more entries.
+ try {
+ bpfMap.insertEntry(testData.keyAt(TEST_MAP_SIZE), testData.valueAt(TEST_MAP_SIZE));
+ fail("Writing too many entries should throw ErrnoException");
+ } catch (ErrnoException expected) {
+ // Expect that can't insert the entry anymore because the number of elements in the
+ // map reached the limit. See man-pages/bpf.
+ assertEquals(OsConstants.E2BIG, expected.errno);
+ }
+ }
+ }
+}
diff --git a/Tethering/tests/unit/Android.bp b/Tethering/tests/unit/Android.bp
index aabaa65..5e4fe52 100644
--- a/Tethering/tests/unit/Android.bp
+++ b/Tethering/tests/unit/Android.bp
@@ -79,6 +79,7 @@
name: "TetheringTestsLib",
defaults: ["TetheringTestsDefaults"],
visibility: [
+ "//frameworks/base/packages/Tethering/tests/integration",
"//packages/modules/Connectivity/Tethering/tests/integration",
]
}
diff --git a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 2eb7589..dae19b7 100644
--- a/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -36,6 +36,7 @@
import static android.net.netlink.StructNdMsg.NUD_FAILED;
import static android.net.netlink.StructNdMsg.NUD_REACHABLE;
import static android.net.netlink.StructNdMsg.NUD_STALE;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.net.module.util.Inet4AddressUtils.intToInet4AddressHTH;
@@ -98,9 +99,13 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.NetworkStackConstants;
import com.android.networkstack.tethering.BpfCoordinator;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.networkstack.tethering.BpfMap;
import com.android.networkstack.tethering.PrivateAddressCoordinator;
+import com.android.networkstack.tethering.TetherIngressKey;
+import com.android.networkstack.tethering.TetherIngressValue;
import com.android.networkstack.tethering.TetheringConfiguration;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
@@ -163,6 +168,7 @@
@Mock private PrivateAddressCoordinator mAddressCoordinator;
@Mock private NetworkStatsManager mStatsManager;
@Mock private TetheringConfiguration mTetherConfig;
+ @Mock private BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
@Captor private ArgumentCaptor<DhcpServingParamsParcel> mDhcpParamsCaptor;
@@ -173,6 +179,7 @@
private InterfaceConfigurationParcel mInterfaceConfiguration;
private NeighborEventConsumer mNeighborEventConsumer;
private BpfCoordinator mBpfCoordinator;
+ private BpfCoordinator.Dependencies mBpfDeps;
private void initStateMachine(int interfaceType) throws Exception {
initStateMachine(interfaceType, false /* usingLegacyDhcp */, DEFAULT_USING_BPF_OFFLOAD);
@@ -256,8 +263,7 @@
mTestAddress);
when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(true /* default value */);
- mBpfCoordinator = spy(new BpfCoordinator(
- new BpfCoordinator.Dependencies() {
+ mBpfDeps = new BpfCoordinator.Dependencies() {
@NonNull
public Handler getHandler() {
return new Handler(mLooper.getLooper());
@@ -282,7 +288,13 @@
public TetheringConfiguration getTetherConfig() {
return mTetherConfig;
}
- }));
+
+ @Nullable
+ public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
+ return mBpfIngressMap;
+ }
+ };
+ mBpfCoordinator = spy(new BpfCoordinator(mBpfDeps));
setUpDhcpServer();
}
@@ -742,14 +754,63 @@
}
@NonNull
+ private static TetherIngressKey makeIngressKey(int upstreamIfindex,
+ @NonNull final InetAddress dst) {
+ return new TetherIngressKey(upstreamIfindex, dst.getAddress());
+ }
+
+ @NonNull
+ private static TetherIngressValue makeIngressValue(@NonNull final MacAddress dstMac) {
+ return new TetherIngressValue(TEST_IFACE_PARAMS.index, dstMac, TEST_IFACE_PARAMS.macAddr,
+ ETH_P_IPV6, NetworkStackConstants.ETHER_MTU);
+ }
+
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ if (inOrder != null) {
+ return inOrder.verify(t);
+ } else {
+ return verify(t);
+ }
+ }
+
+ private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder, int upstreamIfindex,
+ @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception {
+ if (mBpfDeps.isAtLeastS()) {
+ verifyWithOrder(inOrder, mBpfIngressMap).updateEntry(
+ makeIngressKey(upstreamIfindex, dst), makeIngressValue(dstMac));
+ } else {
+ verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(upstreamIfindex, dst,
+ dstMac));
+ }
+ }
+
+ private void verifyNeverTetherOffloadRuleAdd(int upstreamIfindex,
+ @NonNull final InetAddress dst, @NonNull final MacAddress dstMac) throws Exception {
+ if (mBpfDeps.isAtLeastS()) {
+ verify(mBpfIngressMap, never()).updateEntry(makeIngressKey(upstreamIfindex, dst),
+ makeIngressValue(dstMac));
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleAdd(matches(upstreamIfindex, dst, dstMac));
+ }
+ }
+
+ private void verifyNeverTetherOffloadRuleAdd() throws Exception {
+ if (mBpfDeps.isAtLeastS()) {
+ verify(mBpfIngressMap, never()).updateEntry(any(), any());
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ }
+ }
+
+ @NonNull
private static TetherStatsParcel buildEmptyTetherStatsParcel(int ifIndex) {
TetherStatsParcel parcel = new TetherStatsParcel();
parcel.ifIndex = ifIndex;
return parcel;
}
- private void resetNetdAndBpfCoordinator() throws Exception {
- reset(mNetd, mBpfCoordinator);
+ private void resetNetdBpfMapAndCoordinator() throws Exception {
+ reset(mNetd, mBpfIngressMap, mBpfCoordinator);
when(mNetd.tetherOffloadGetStats()).thenReturn(new TetherStatsParcel[0]);
when(mNetd.tetherOffloadGetAndClearStats(UPSTREAM_IFINDEX))
.thenReturn(buildEmptyTetherStatsParcel(UPSTREAM_IFINDEX));
@@ -774,34 +835,34 @@
final MacAddress macA = MacAddress.fromString("00:00:00:00:00:0a");
final MacAddress macB = MacAddress.fromString("11:22:33:00:00:0b");
- resetNetdAndBpfCoordinator();
- verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+ resetNetdBpfMapAndCoordinator();
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
// TODO: Perhaps verify the interaction of tetherOffloadSetInterfaceQuota and
// tetherOffloadGetAndClearStats in netd while the rules are changed.
// Events on other interfaces are ignored.
recvNewNeigh(notMyIfindex, neighA, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
// Events on this interface are received and sent to netd.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
- resetNetdAndBpfCoordinator();
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA);
+ resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
- resetNetdAndBpfCoordinator();
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
+ resetNetdBpfMapAndCoordinator();
// Link-local and multicast neighbors are ignored.
recvNewNeigh(myIfindex, neighLL, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
recvNewNeigh(myIfindex, neighMC, NUD_REACHABLE, macA);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
// A neighbor that is no longer valid causes the rule to be removed.
// NUD_FAILED events do not have a MAC address.
@@ -809,30 +870,30 @@
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macNull));
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macNull));
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
// A neighbor that is deleted causes the rule to be removed.
recvDelNeigh(myIfindex, neighB, NUD_STALE, macB);
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macNull));
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macNull));
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
// Upstream changes result in updating the rules.
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
- InOrder inOrder = inOrder(mNetd);
+ InOrder inOrder = inOrder(mNetd, mBpfIngressMap);
LinkProperties lp = new LinkProperties();
lp.setInterfaceName(UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2, lp, -1);
verify(mBpfCoordinator).tetherOffloadRuleUpdate(mIpServer, UPSTREAM_IFINDEX2);
inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighA, macA));
+ verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighA, macA);
inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX2, neighB, macB));
- resetNetdAndBpfCoordinator();
+ verifyTetherOffloadRuleAdd(inOrder, UPSTREAM_IFINDEX2, neighB, macB);
+ resetNetdBpfMapAndCoordinator();
// When the upstream is lost, rules are removed.
dispatchTetherConnectionChanged(null, null, 0);
@@ -843,15 +904,15 @@
verify(mBpfCoordinator, times(2)).tetherOffloadRuleClear(mIpServer);
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighA, macA));
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX2, neighB, macB));
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
// If the upstream is IPv4-only, no rules are added.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neighA, NUD_REACHABLE, macA);
// Clear function is called by #updateIpv6ForwardingRules for the IPv6 upstream is lost.
verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
- verifyNoMoreInteractions(mBpfCoordinator, mNetd);
+ verifyNoMoreInteractions(mBpfCoordinator, mNetd, mBpfIngressMap);
// Rules can be added again once upstream IPv6 connectivity is available.
lp.setInterfaceName(UPSTREAM_IFACE);
@@ -859,13 +920,13 @@
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
- verify(mNetd, never()).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
+ verifyNeverTetherOffloadRuleAdd(UPSTREAM_IFINDEX, neighA, macA);
// If upstream IPv6 connectivity is lost, rules are removed.
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
dispatchTetherConnectionChanged(UPSTREAM_IFACE, null, 0);
verify(mBpfCoordinator).tetherOffloadRuleClear(mIpServer);
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
@@ -877,11 +938,11 @@
recvNewNeigh(myIfindex, neighB, NUD_REACHABLE, macB);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighA, macA));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighA, macA));
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighA, macA);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neighB, macB));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neighB, macB));
- resetNetdAndBpfCoordinator();
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neighB, macB);
+ resetNetdBpfMapAndCoordinator();
mIpServer.stop();
mLooper.dispatchAll();
@@ -889,7 +950,7 @@
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighA, macA));
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neighB, macB));
verify(mIpNeighborMonitor).stop();
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
}
@Test
@@ -910,35 +971,35 @@
// A neighbor that is added or deleted causes the rule to be added or removed.
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
true /* usingBpfOffload */);
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
verify(mBpfCoordinator).tetherOffloadRuleAdd(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macA));
- verify(mNetd).tetherOffloadRuleAdd(matches(UPSTREAM_IFINDEX, neigh, macA));
- resetNetdAndBpfCoordinator();
+ verifyTetherOffloadRuleAdd(null, UPSTREAM_IFINDEX, neigh, macA);
+ resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
verify(mBpfCoordinator).tetherOffloadRuleRemove(
mIpServer, makeForwardingRule(UPSTREAM_IFINDEX, neigh, macNull));
verify(mNetd).tetherOffloadRuleRemove(matches(UPSTREAM_IFINDEX, neigh, macNull));
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
// [2] Disable BPF offload.
// A neighbor that is added or deleted doesn’t cause the rule to be added or removed.
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE, false /* usingLegacyDhcp */,
false /* usingBpfOffload */);
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
recvNewNeigh(myIfindex, neigh, NUD_REACHABLE, macA);
verify(mBpfCoordinator, never()).tetherOffloadRuleAdd(any(), any());
- verify(mNetd, never()).tetherOffloadRuleAdd(any());
- resetNetdAndBpfCoordinator();
+ verifyNeverTetherOffloadRuleAdd();
+ resetNetdBpfMapAndCoordinator();
recvDelNeigh(myIfindex, neigh, NUD_STALE, macA);
verify(mBpfCoordinator, never()).tetherOffloadRuleRemove(any(), any());
verify(mNetd, never()).tetherOffloadRuleRemove(any());
- resetNetdAndBpfCoordinator();
+ resetNetdBpfMapAndCoordinator();
}
@Test
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
index 64242ae..b920fa8 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/BpfCoordinatorTest.java
@@ -24,6 +24,7 @@
import static android.net.NetworkStats.UID_ALL;
import static android.net.NetworkStats.UID_TETHERING;
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
+import static android.system.OsConstants.ETH_P_IPV6;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType;
import static com.android.networkstack.tethering.BpfCoordinator.StatsType.STATS_PER_IFACE;
@@ -33,6 +34,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
@@ -40,8 +42,10 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -54,6 +58,7 @@
import android.net.TetherStatsParcel;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
+import android.os.Build;
import android.os.Handler;
import android.os.test.TestLooper;
@@ -62,7 +67,9 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.NetworkStackConstants;
import com.android.networkstack.tethering.BpfCoordinator.Ipv6ForwardingRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import com.android.testutils.TestableNetworkStatsProviderCbBinder;
import org.junit.Before;
@@ -94,6 +101,7 @@
@Mock private INetd mNetd;
@Mock private IpServer mIpServer;
@Mock private TetheringConfiguration mTetherConfig;
+ @Mock private BpfMap<TetherIngressKey, TetherIngressValue> mBpfIngressMap;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
@@ -102,32 +110,37 @@
ArgumentCaptor.forClass(ArrayList.class);
private final TestLooper mTestLooper = new TestLooper();
private BpfCoordinator.Dependencies mDeps =
- new BpfCoordinator.Dependencies() {
- @NonNull
- public Handler getHandler() {
- return new Handler(mTestLooper.getLooper());
- }
+ spy(new BpfCoordinator.Dependencies() {
+ @NonNull
+ public Handler getHandler() {
+ return new Handler(mTestLooper.getLooper());
+ }
- @NonNull
- public INetd getNetd() {
- return mNetd;
- }
+ @NonNull
+ public INetd getNetd() {
+ return mNetd;
+ }
- @NonNull
- public NetworkStatsManager getNetworkStatsManager() {
- return mStatsManager;
- }
+ @NonNull
+ public NetworkStatsManager getNetworkStatsManager() {
+ return mStatsManager;
+ }
- @NonNull
- public SharedLog getSharedLog() {
- return new SharedLog("test");
- }
+ @NonNull
+ public SharedLog getSharedLog() {
+ return new SharedLog("test");
+ }
- @Nullable
- public TetheringConfiguration getTetherConfig() {
- return mTetherConfig;
- }
- };
+ @Nullable
+ public TetheringConfiguration getTetherConfig() {
+ return mTetherConfig;
+ }
+
+ @Nullable
+ public BpfMap<TetherIngressKey, TetherIngressValue> getBpfIngressMap() {
+ return mBpfIngressMap;
+ }
+ });
@Before public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -185,6 +198,63 @@
waitForIdle();
}
+ private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
+ if (inOrder != null) {
+ return inOrder.verify(t);
+ } else {
+ return verify(t);
+ }
+ }
+
+ private void verifyTetherOffloadRuleAdd(@Nullable InOrder inOrder,
+ @NonNull Ipv6ForwardingRule rule) throws Exception {
+ if (mDeps.isAtLeastS()) {
+ verifyWithOrder(inOrder, mBpfIngressMap).updateEntry(
+ rule.makeTetherIngressKey(), rule.makeTetherIngressValue());
+ } else {
+ verifyWithOrder(inOrder, mNetd).tetherOffloadRuleAdd(matches(rule));
+ }
+ }
+
+ private void verifyNeverTetherOffloadRuleAdd() throws Exception {
+ if (mDeps.isAtLeastS()) {
+ verify(mBpfIngressMap, never()).updateEntry(any(), any());
+ } else {
+ verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ }
+ }
+
+ // TODO: remove once presubmit tests on R even the code is submitted on S.
+ private void checkTetherOffloadRuleAdd(boolean usingApiS) throws Exception {
+ setupFunctioningNetdInterface();
+
+ // Replace Dependencies#isAtLeastS() for testing R and S+ BPF map apis. Note that |mDeps|
+ // must be mocked before calling #makeBpfCoordinator which use |mDeps| to initialize the
+ // coordinator.
+ doReturn(usingApiS).when(mDeps).isAtLeastS();
+ final BpfCoordinator coordinator = makeBpfCoordinator();
+
+ final String mobileIface = "rmnet_data0";
+ final Integer mobileIfIndex = 100;
+ coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
+
+ final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+ coordinator.tetherOffloadRuleAdd(mIpServer, rule);
+ verifyTetherOffloadRuleAdd(null, rule);
+ }
+
+ // TODO: remove once presubmit tests on R even the code is submitted on S.
+ @Test
+ public void testTetherOffloadRuleAddSdkR() throws Exception {
+ checkTetherOffloadRuleAdd(false /* R */);
+ }
+
+ // TODO: remove once presubmit tests on R even the code is submitted on S.
+ @Test
+ public void testTetherOffloadRuleAddAtLeastSdkS() throws Exception {
+ checkTetherOffloadRuleAdd(true /* S+ */);
+ }
+
@Test
public void testGetForwardedStats() throws Exception {
setupFunctioningNetdInterface();
@@ -339,6 +409,33 @@
}
@Test
+ public void testRuleMakeTetherIngressKey() throws Exception {
+ final Integer mobileIfIndex = 100;
+ final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+
+ final TetherIngressKey key = rule.makeTetherIngressKey();
+ assertEquals(key.iif, (long) mobileIfIndex);
+ assertTrue(Arrays.equals(key.neigh6, NEIGH_A.getAddress()));
+ // iif (4) + neigh6 (16) = 20.
+ assertEquals(20, key.writeToBytes().length);
+ }
+
+ @Test
+ public void testRuleMakeTetherIngressValue() throws Exception {
+ final Integer mobileIfIndex = 100;
+ final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
+
+ final TetherIngressValue value = rule.makeTetherIngressValue();
+ assertEquals(value.oif, DOWNSTREAM_IFINDEX);
+ assertEquals(value.ethDstMac, MAC_A);
+ assertEquals(value.ethSrcMac, DOWNSTREAM_MAC);
+ assertEquals(value.ethProto, ETH_P_IPV6);
+ assertEquals(value.pmtu, NetworkStackConstants.ETHER_MTU);
+ // oif (4) + ethDstMac (6) + ethSrcMac (6) + ethProto (2) + pmtu (2) = 20.
+ assertEquals(20, value.writeToBytes().length);
+ }
+
+ @Test
public void testSetDataLimit() throws Exception {
setupFunctioningNetdInterface();
@@ -352,9 +449,9 @@
// Set the unlimited quota as default if the service has never applied a data limit for a
// given upstream. Note that the data limit only be applied on an upstream which has rules.
final Ipv6ForwardingRule rule = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
- final InOrder inOrder = inOrder(mNetd);
+ final InOrder inOrder = inOrder(mNetd, mBpfIngressMap);
coordinator.tetherOffloadRuleAdd(mIpServer, rule);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(rule));
+ verifyTetherOffloadRuleAdd(inOrder, rule);
inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED);
inOrder.verifyNoMoreInteractions();
@@ -393,7 +490,7 @@
// Applying a data limit to the current upstream does not take any immediate action.
// The data limit could be only set on an upstream which has rules.
final long limit = 12345;
- final InOrder inOrder = inOrder(mNetd);
+ final InOrder inOrder = inOrder(mNetd, mBpfIngressMap);
mTetherStatsProvider.onSetLimit(mobileIface, limit);
waitForIdle();
inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong());
@@ -401,14 +498,14 @@
// Adding the first rule on current upstream immediately sends the quota to netd.
final Ipv6ForwardingRule ruleA = buildTestForwardingRule(mobileIfIndex, NEIGH_A, MAC_A);
coordinator.tetherOffloadRuleAdd(mIpServer, ruleA);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleA));
+ verifyTetherOffloadRuleAdd(inOrder, ruleA);
inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, limit);
inOrder.verifyNoMoreInteractions();
// Adding the second rule on current upstream does not send the quota to netd.
final Ipv6ForwardingRule ruleB = buildTestForwardingRule(mobileIfIndex, NEIGH_B, MAC_B);
coordinator.tetherOffloadRuleAdd(mIpServer, ruleB);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ruleB));
+ verifyTetherOffloadRuleAdd(inOrder, ruleB);
inOrder.verify(mNetd, never()).tetherOffloadSetInterfaceQuota(anyInt(), anyLong());
// Removing the second rule on current upstream does not send the quota to netd.
@@ -438,7 +535,7 @@
coordinator.addUpstreamNameToLookupTable(ethIfIndex, ethIface);
coordinator.addUpstreamNameToLookupTable(mobileIfIndex, mobileIface);
- final InOrder inOrder = inOrder(mNetd);
+ final InOrder inOrder = inOrder(mNetd, mBpfIngressMap);
// Before the rule test, here are the additional actions while the rules are changed.
// - After adding the first rule on a given upstream, the coordinator adds a data limit.
@@ -455,11 +552,11 @@
ethIfIndex, NEIGH_B, MAC_B);
coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleA);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleA));
+ verifyTetherOffloadRuleAdd(inOrder, ethernetRuleA);
inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(ethIfIndex, QUOTA_UNLIMITED);
coordinator.tetherOffloadRuleAdd(mIpServer, ethernetRuleB);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(ethernetRuleB));
+ verifyTetherOffloadRuleAdd(inOrder, ethernetRuleB);
// [2] Update the existing rules from Ethernet to cellular.
final Ipv6ForwardingRule mobileRuleA = buildTestForwardingRule(
@@ -473,11 +570,11 @@
// by one for updating upstream interface index by #tetherOffloadRuleUpdate.
coordinator.tetherOffloadRuleUpdate(mIpServer, mobileIfIndex);
inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleA));
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleA));
+ verifyTetherOffloadRuleAdd(inOrder, mobileRuleA);
inOrder.verify(mNetd).tetherOffloadSetInterfaceQuota(mobileIfIndex, QUOTA_UNLIMITED);
inOrder.verify(mNetd).tetherOffloadRuleRemove(matches(ethernetRuleB));
inOrder.verify(mNetd).tetherOffloadGetAndClearStats(ethIfIndex);
- inOrder.verify(mNetd).tetherOffloadRuleAdd(matches(mobileRuleB));
+ verifyTetherOffloadRuleAdd(inOrder, mobileRuleB);
// [3] Clear all rules for a given IpServer.
when(mNetd.tetherOffloadGetAndClearStats(mobileIfIndex))
@@ -499,11 +596,10 @@
.addEntry(buildTestEntry(STATS_PER_UID, mobileIface, 50, 60, 70, 80)));
}
- @Test
- public void testTetheringConfigDisable() throws Exception {
- setupFunctioningNetdInterface();
- when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
-
+ private void checkBpfDisabled() throws Exception {
+ // The caller may mock the global dependencies |mDeps| which is used in
+ // #makeBpfCoordinator for testing.
+ // See #testBpfDisabledbyNoBpfIngressMap.
final BpfCoordinator coordinator = makeBpfCoordinator();
coordinator.startPolling();
@@ -523,7 +619,7 @@
final MacAddress mac = MacAddress.fromString("00:00:00:00:00:0a");
final Ipv6ForwardingRule rule = buildTestForwardingRule(ifIndex, neigh, mac);
coordinator.tetherOffloadRuleAdd(mIpServer, rule);
- verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ verifyNeverTetherOffloadRuleAdd();
LinkedHashMap<Inet6Address, Ipv6ForwardingRule> rules =
coordinator.getForwardingRulesForTesting().get(mIpServer);
assertNull(rules);
@@ -550,13 +646,30 @@
// The rule can't be updated.
coordinator.tetherOffloadRuleUpdate(mIpServer, rule.upstreamIfindex + 1 /* new */);
verify(mNetd, never()).tetherOffloadRuleRemove(any());
- verify(mNetd, never()).tetherOffloadRuleAdd(any());
+ verifyNeverTetherOffloadRuleAdd();
rules = coordinator.getForwardingRulesForTesting().get(mIpServer);
assertNotNull(rules);
assertEquals(1, rules.size());
}
@Test
+ public void testBpfDisabledbyConfig() throws Exception {
+ setupFunctioningNetdInterface();
+ when(mTetherConfig.isBpfOffloadEnabled()).thenReturn(false);
+
+ checkBpfDisabled();
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ public void testBpfDisabledbyNoBpfIngressMap() throws Exception {
+ setupFunctioningNetdInterface();
+ doReturn(null).when(mDeps).getBpfIngressMap();
+
+ checkBpfDisabled();
+ }
+
+ @Test
public void testTetheringConfigSetPollingInterval() throws Exception {
setupFunctioningNetdInterface();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
index f4d2489..071a290 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/MockTetheringService.java
@@ -27,6 +27,7 @@
import android.os.IBinder;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
public class MockTetheringService extends TetheringService {
private final Tethering mTethering = mock(Tethering.class);
@@ -43,7 +44,8 @@
@Override
boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
- @NonNull String callingPackage, boolean throwException) {
+ @NonNull String callingPackage, @Nullable String callingAttributionTag,
+ boolean throwException) {
// Test this does not verify the calling package / UID, as calling package could be shell
// and not match the UID.
return context.checkCallingOrSelfPermission(WRITE_SETTINGS) == PERMISSION_GRANTED;
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
index 22d894b..7bba67b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringServiceTest.java
@@ -18,7 +18,6 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.TETHER_PRIVILEGED;
-import static android.Manifest.permission.UPDATE_APP_OPS_STATS;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.net.TetheringManager.TETHERING_WIFI;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
@@ -62,6 +61,7 @@
public final class TetheringServiceTest {
private static final String TEST_IFACE_NAME = "test_wlan0";
private static final String TEST_CALLER_PKG = "com.android.shell";
+ private static final String TEST_ATTRIBUTION_TAG = null;
@Mock private ITetheringEventCallback mITetheringEventCallback;
@Rule public ServiceTestRule mServiceTestRule;
private Tethering mTethering;
@@ -135,7 +135,7 @@
}
private void runAsWriteSettings(final TestTetheringCall test) throws Exception {
- runTetheringCall(test, WRITE_SETTINGS, UPDATE_APP_OPS_STATS);
+ runTetheringCall(test, WRITE_SETTINGS);
}
private void runTetheringCall(final TestTetheringCall test, String... permissions)
@@ -157,7 +157,7 @@
private void runTether(final TestTetheringResult result) throws Exception {
when(mTethering.tether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
- mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
verify(mTethering).tether(TEST_IFACE_NAME);
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -166,7 +166,8 @@
@Test
public void testTether() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ mTetheringConnector.tether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -186,7 +187,8 @@
private void runUnTether(final TestTetheringResult result) throws Exception {
when(mTethering.untether(TEST_IFACE_NAME)).thenReturn(TETHER_ERROR_NO_ERROR);
- mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetheringSupported();
verify(mTethering).untether(TEST_IFACE_NAME);
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -195,7 +197,8 @@
@Test
public void testUntether() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, result);
+ mTetheringConnector.untether(TEST_IFACE_NAME, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -215,7 +218,8 @@
private void runSetUsbTethering(final TestTetheringResult result) throws Exception {
when(mTethering.setUsbTethering(true /* enable */)).thenReturn(TETHER_ERROR_NO_ERROR);
- mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+ mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
verify(mTethering).setUsbTethering(true /* enable */);
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -224,7 +228,8 @@
@Test
public void testSetUsbTethering() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG, result);
+ mTetheringConnector.setUsbTethering(true /* enable */, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -245,7 +250,8 @@
private void runStartTethering(final TestTetheringResult result,
final TetheringRequestParcel request) throws Exception {
- mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetheringSupported();
verify(mTethering).startTethering(eq(request), eq(result));
}
@@ -256,7 +262,8 @@
request.tetheringType = TETHERING_WIFI;
runAsNoPermission((result) -> {
- mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -279,7 +286,8 @@
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = TETHERING_WIFI;
request.exemptFromEntitlementCheck = true;
- mTetheringConnector.startTethering(request, TEST_CALLER_PKG, result);
+ mTetheringConnector.startTethering(request, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
}
@@ -304,7 +312,8 @@
}
private void runStopTethering(final TestTetheringResult result) throws Exception {
- mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+ mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
verify(mTethering).stopTethering(TETHERING_WIFI);
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -313,7 +322,8 @@
@Test
public void testStopTethering() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG, result);
+ mTetheringConnector.stopTethering(TETHERING_WIFI, TEST_CALLER_PKG,
+ TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -334,7 +344,7 @@
private void runRequestLatestTetheringEntitlementResult() throws Exception {
final MyResultReceiver result = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
- true /* showEntitlementUi */, TEST_CALLER_PKG);
+ true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetheringSupported();
verify(mTethering).requestLatestTetheringEntitlementResult(eq(TETHERING_WIFI),
eq(result), eq(true) /* showEntitlementUi */);
@@ -345,7 +355,7 @@
// Run as no permission.
final MyResultReceiver result = new MyResultReceiver(null);
mTetheringConnector.requestLatestTetheringEntitlementResult(TETHERING_WIFI, result,
- true /* showEntitlementUi */, TEST_CALLER_PKG);
+ true /* showEntitlementUi */, TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractions(mTethering);
@@ -417,7 +427,7 @@
}
private void runStopAllTethering(final TestTetheringResult result) throws Exception {
- mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+ mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
verify(mTethering).untetherAll();
result.assertResult(TETHER_ERROR_NO_ERROR);
@@ -426,7 +436,7 @@
@Test
public void testStopAllTethering() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, result);
+ mTetheringConnector.stopAllTethering(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
@@ -445,7 +455,7 @@
}
private void runIsTetheringSupported(final TestTetheringResult result) throws Exception {
- mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+ mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG, result);
verify(mTethering).isTetheringSupported();
result.assertResult(TETHER_ERROR_NO_ERROR);
}
@@ -453,7 +463,8 @@
@Test
public void testIsTetheringSupported() throws Exception {
runAsNoPermission((result) -> {
- mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, result);
+ mTetheringConnector.isTetheringSupported(TEST_CALLER_PKG, TEST_ATTRIBUTION_TAG,
+ result);
verify(mTethering).isTetherProvisioningRequired();
result.assertResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
verifyNoMoreInteractionsForTethering();
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index bdc695c..f4b3749 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -136,6 +136,7 @@
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -864,6 +865,14 @@
mLooper.dispatchAll();
}
+ private void assertSetIfaceToDadProxy(final int numOfCalls, final String ifaceName) {
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R || "S".equals(Build.VERSION.CODENAME)
+ || "T".equals(Build.VERSION.CODENAME)) {
+ verify(mDadProxy, times(numOfCalls)).setUpstreamIface(
+ argThat(ifaceParams -> ifaceName.equals(ifaceParams.name)));
+ }
+ }
+
@Test
public void workingMobileUsbTethering_IPv4() throws Exception {
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
@@ -873,7 +882,7 @@
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
- verify(mDadProxy, never()).setUpstreamIface(notNull());
+ assertSetIfaceToDadProxy(0 /* numOfCalls */, "" /* ifaceName */);
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).startWithCallbacks(
any(), any());
@@ -901,7 +910,7 @@
sendIPv6TetherUpdates(upstreamState);
// TODO: add interfaceParams to compare in verify.
- verify(mDadProxy, times(1)).setUpstreamIface(notNull());
+ assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
@@ -918,7 +927,7 @@
any(), any());
sendIPv6TetherUpdates(upstreamState);
- verify(mDadProxy, times(1)).setUpstreamIface(notNull());
+ assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
@@ -936,7 +945,7 @@
verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
- verify(mDadProxy, times(1)).setUpstreamIface(notNull());
+ assertSetIfaceToDadProxy(1 /* numOfCalls */, TEST_MOBILE_IFNAME /* ifaceName */);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
verify(mNetd, times(1)).tetherApplyDnsInterfaces();
}
diff --git a/framework/Android.bp b/framework/Android.bp
new file mode 100644
index 0000000..8db8d76
--- /dev/null
+++ b/framework/Android.bp
@@ -0,0 +1,29 @@
+//
+// 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.
+//
+
+// TODO: use a java_library in the bootclasspath instead
+filegroup {
+ name: "framework-connectivity-sources",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.aidl",
+ ],
+ path: "src",
+ visibility: [
+ "//frameworks/base",
+ "//packages/modules/Connectivity:__subpackages__",
+ ],
+}
\ No newline at end of file
diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl b/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
new file mode 100644
index 0000000..1af9e76
--- /dev/null
+++ b/framework/src/com/android/connectivity/aidl/INetworkAgent.aidl
@@ -0,0 +1,46 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.NattKeepalivePacketData;
+import android.net.TcpKeepalivePacketData;
+
+import com.android.connectivity.aidl.INetworkAgentRegistry;
+
+/**
+ * Interface to notify NetworkAgent of connectivity events.
+ * @hide
+ */
+oneway interface INetworkAgent {
+ void onRegistered(in INetworkAgentRegistry registry);
+ void onDisconnected();
+ void onBandwidthUpdateRequested();
+ void onValidationStatusChanged(int validationStatus,
+ in @nullable String captivePortalUrl);
+ void onSaveAcceptUnvalidated(boolean acceptUnvalidated);
+ void onStartNattSocketKeepalive(int slot, int intervalDurationMs,
+ in NattKeepalivePacketData packetData);
+ void onStartTcpSocketKeepalive(int slot, int intervalDurationMs,
+ in TcpKeepalivePacketData packetData);
+ void onStopSocketKeepalive(int slot);
+ void onSignalStrengthThresholdsUpdated(in int[] thresholds);
+ void onPreventAutomaticReconnect();
+ void onAddNattKeepalivePacketFilter(int slot,
+ in NattKeepalivePacketData packetData);
+ void onAddTcpKeepalivePacketFilter(int slot,
+ in TcpKeepalivePacketData packetData);
+ void onRemoveKeepalivePacketFilter(int slot);
+}
diff --git a/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
new file mode 100644
index 0000000..d42a340
--- /dev/null
+++ b/framework/src/com/android/connectivity/aidl/INetworkAgentRegistry.aidl
@@ -0,0 +1,36 @@
+/**
+ * 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 perNmissions and
+ * limitations under the License.
+ */
+package com.android.connectivity.aidl;
+
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+
+/**
+ * Interface for NetworkAgents to send network network properties.
+ * @hide
+ */
+oneway interface INetworkAgentRegistry {
+ void sendNetworkCapabilities(in NetworkCapabilities nc);
+ void sendLinkProperties(in LinkProperties lp);
+ // TODO: consider replacing this by "markConnected()" and removing
+ void sendNetworkInfo(in NetworkInfo info);
+ void sendScore(int score);
+ void sendExplicitlySelected(boolean explicitlySelected, boolean acceptPartial);
+ void sendSocketKeepaliveEvent(int slot, int reason);
+ void sendUnderlyingNetworks(in @nullable List<Network> networks);
+}
diff --git a/tests/cts/tethering/OWNERS b/tests/cts/OWNERS
similarity index 81%
rename from tests/cts/tethering/OWNERS
rename to tests/cts/OWNERS
index cd6abeb..4264345 100644
--- a/tests/cts/tethering/OWNERS
+++ b/tests/cts/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 31808
+set noparent
lorenzo@google.com
-satk@google.com
-
+satk@google.com
\ No newline at end of file
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index 741c961..47b114b 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -24,7 +24,6 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
}
diff --git a/tests/cts/hostside/OWNERS b/tests/cts/hostside/OWNERS
index 52c8053..20bc55e 100644
--- a/tests/cts/hostside/OWNERS
+++ b/tests/cts/hostside/OWNERS
@@ -1,4 +1,4 @@
# Bug component: 61373
+# Inherits parent owners
sudheersai@google.com
-lorenzo@google.com
jchalard@google.com
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index e129be7..9903756 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -35,7 +35,6 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
}
diff --git a/tests/cts/hostside/app/AndroidManifest.xml b/tests/cts/hostside/app/AndroidManifest.xml
index 3940de4..e5bae5f 100644
--- a/tests/cts/hostside/app/AndroidManifest.xml
+++ b/tests/cts/hostside/app/AndroidManifest.xml
@@ -15,42 +15,42 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.net.hostside">
+ package="com.android.cts.net.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.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.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"/>
- <application android:requestLegacyExternalStorage="true" >
- <uses-library android:name="android.test.runner" />
- <activity android:name=".MyActivity" />
+ <application android:requestLegacyExternalStorage="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name=".MyActivity"/>
<service android:name=".MyVpnService"
- android:permission="android.permission.BIND_VPN_SERVICE">
+ android:permission="android.permission.BIND_VPN_SERVICE"
+ android:exported="true">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
- <service
- android:name=".MyNotificationListenerService"
- android:label="MyNotificationListenerService"
- android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" >
+ <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" />
+ <action android:name="android.service.notification.NotificationListenerService"/>
</intent-filter>
</service>
</application>
- <instrumentation
- android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="com.android.cts.net.hostside" />
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.cts.net.hostside"/>
</manifest>
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 219cc3d..f9e30b6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -67,7 +67,7 @@
setAppIdle(true);
launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
finishActivity();
- assertAppIdle(false); // Sanity check - not idle anymore, since activity was launched...
+ assertAppIdle(false); // verify - not idle anymore, since activity was launched...
assertBackgroundNetworkAccess(true);
setAppIdle(true);
assertBackgroundNetworkAccess(false);
@@ -78,6 +78,17 @@
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
@@ -86,29 +97,29 @@
assertBackgroundNetworkAccess(false);
addPowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // Sanity check - not idle anymore, since whitelisted
+ assertAppIdle(false); // verify - not idle anymore, since whitelisted
assertBackgroundNetworkAccess(true);
setAppIdleNoAssert(true);
assertAppIdle(false); // app is still whitelisted
removePowerSaveModeWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // Sanity check - idle again, once whitelisted was removed
+ assertAppIdle(true); // verify - idle again, once whitelisted was removed
assertBackgroundNetworkAccess(false);
setAppIdle(true);
addPowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(false); // Sanity check - not idle anymore, since whitelisted
+ assertAppIdle(false); // verify - not idle anymore, since whitelisted
assertBackgroundNetworkAccess(true);
setAppIdleNoAssert(true);
assertAppIdle(false); // app is still whitelisted
removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
- assertAppIdle(true); // Sanity check - idle again, once whitelisted was removed
+ assertAppIdle(true); // verify - idle again, once whitelisted was removed
assertBackgroundNetworkAccess(false);
assertsForegroundAlwaysHasNetworkAccess();
- // Sanity check - no whitelist, no access!
+ // verify - no whitelist, no access!
setAppIdle(true);
assertBackgroundNetworkAccess(false);
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index 6f32c56..e0ce4ea 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
@@ -101,7 +101,7 @@
@Test
public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
throws Exception {
- setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
+ setPendingIntentAllowlistDuration(NETWORK_TIMEOUT_MS);
try {
registerNotificationListenerService();
setDozeMode(true);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index e5fd149..e2dc1a1 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -25,7 +25,6 @@
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
@@ -46,15 +45,16 @@
import android.net.ConnectivityManager;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkInfo.State;
-import android.net.wifi.WifiManager;
import android.os.BatteryManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.SystemClock;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.service.notification.NotificationListenerService;
import android.util.Log;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+
import org.junit.Rule;
import org.junit.rules.RuleChain;
import org.junit.runner.RunWith;
@@ -130,24 +130,24 @@
protected int mUid;
private int mMyUid;
private MyServiceClient mServiceClient;
- private String mDeviceIdleConstantsSetting;
+ private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
@Rule
public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
.around(new MeterednessConfigurationRule());
protected void setUp() throws Exception {
-
PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
.getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
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);
mServiceClient.bind();
- mDeviceIdleConstantsSetting = "device_idle_constants";
executeShellCommand("cmd netpolicy start-watching " + mUid);
setAppIdle(false);
@@ -229,21 +229,25 @@
}
protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
- assertBackgroundState(); // Sanity check.
+ assertBackgroundState();
assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
}
protected void assertForegroundNetworkAccess() throws Exception {
- assertForegroundState(); // Sanity check.
+ assertForegroundNetworkAccess(true);
+ }
+
+ protected void assertForegroundNetworkAccess(boolean expectAllowed) throws Exception {
+ assertForegroundState();
// We verified that app is in foreground state but if the screen turns-off while
// verifying for network access, the app will go into background state (in case app's
// foreground status was due to top activity). So, turn the screen on when verifying
// network connectivity.
- assertNetworkAccess(true /* expectAvailable */, true /* needScreenOn */);
+ assertNetworkAccess(expectAllowed /* expectAvailable */, true /* needScreenOn */);
}
protected void assertForegroundServiceNetworkAccess() throws Exception {
- assertForegroundServiceState(); // Sanity check.
+ assertForegroundServiceState();
assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
}
@@ -374,7 +378,7 @@
}
// Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
- assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check
+ assertEquals("Wrong network status: " + resultData, 5, parts.length);
final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
final DetailedState detailedState = parts[1].equals("null")
? null : DetailedState.valueOf(parts[1]);
@@ -547,7 +551,7 @@
// 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); // Sanity check
+ assertPowerSaveModeWhitelist(packageName, true);
}
protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
@@ -555,7 +559,7 @@
// 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); // Sanity check
+ assertPowerSaveModeWhitelist(packageName, false);
}
protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
@@ -571,7 +575,7 @@
// 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); // Sanity check
+ assertPowerSaveModeExceptIdleWhitelist(packageName, true);
}
protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
@@ -580,7 +584,7 @@
// 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); // Sanity check
+ assertPowerSaveModeExceptIdleWhitelist(packageName, false);
}
protected void turnBatteryOn() throws Exception {
@@ -635,7 +639,7 @@
}
protected void setDozeMode(boolean enabled) throws Exception {
- // Sanity check, since tests should check beforehand....
+ // Check doze mode is supported.
assertTrue("Device does not support Doze Mode", isDozeModeSupported());
Log.i(TAG, "Setting Doze Mode to " + enabled);
@@ -648,7 +652,6 @@
turnBatteryOff();
executeShellCommand("dumpsys deviceidle unforce");
}
- // Sanity check.
assertDozeMode(enabled);
}
@@ -659,7 +662,7 @@
protected void setAppIdle(boolean enabled) throws Exception {
Log.i(TAG, "Setting app idle to " + enabled);
executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
- assertAppIdle(enabled); // Sanity check
+ assertAppIdle(enabled);
}
protected void setAppIdleNoAssert(boolean enabled) throws Exception {
@@ -722,15 +725,13 @@
nm.isNotificationListenerAccessGranted(listenerComponent));
}
- protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception {
- executeSilentShellCommand(String.format(
- "settings put global %s %s=%d", mDeviceIdleConstantsSetting,
- "notification_whitelist_duration", durationMs));
+ protected void setPendingIntentAllowlistDuration(long durationMs) {
+ mDeviceIdleDeviceConfigStateHelper.set("notification_allowlist_duration_ms",
+ String.valueOf(durationMs));
}
- protected void resetDeviceIdleSettings() throws Exception {
- executeShellCommand(String.format("settings delete global %s",
- mDeviceIdleConstantsSetting));
+ protected void resetDeviceIdleSettings() {
+ mDeviceIdleDeviceConfigStateHelper.restoreOriginalValues();
}
protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index aa2c914..604a0b6 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -67,7 +67,7 @@
public void testGetRestrictBackgroundStatus_disabled() throws Exception {
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
- // Sanity check: make sure status is always disabled, never whitelisted
+ // Verify status is always disabled, never whitelisted
addRestrictBackgroundWhitelist(mUid);
assertRestrictBackgroundChangedReceived(0);
assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 3807d79..3041dfa 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -55,7 +55,7 @@
public class NetworkPolicyTestUtils {
- private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
+ private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 10_000;
private static ConnectivityManager mCm;
private static WifiManager mWm;
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
new file mode 100644
index 0000000..29d3c6e
--- /dev/null
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/RestrictedModeTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.net.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();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ setRestrictedMode(false);
+ super.tearDown();
+ }
+
+ private void setRestrictedMode(boolean enabled) throws Exception {
+ executeSilentShellCommand(
+ "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
+ assertRestrictedModeState(enabled);
+ }
+
+ private void assertRestrictedModeState(boolean enabled) throws Exception {
+ assertDelayedShellCommand("cmd netpolicy get restricted-mode",
+ "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
+ }
+
+ @Test
+ public void testNetworkAccess() throws Exception {
+ setRestrictedMode(false);
+
+ // go to foreground state and enable restricted mode
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ setRestrictedMode(true);
+ assertForegroundNetworkAccess(false);
+
+ // go to background state
+ finishActivity();
+ assertBackgroundNetworkAccess(false);
+
+ // disable restricted mode and assert network access in foreground and background states
+ setRestrictedMode(false);
+ launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
+ assertForegroundNetworkAccess(true);
+
+ // go to background state
+ finishActivity();
+ assertBackgroundNetworkAccess(true);
+ }
+}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
index a451ea8..81a431c 100755
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -17,13 +17,26 @@
package com.android.cts.net.hostside;
import static android.os.Process.INVALID_UID;
-import static android.system.OsConstants.*;
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.ECONNABORTED;
+import static android.system.OsConstants.IPPROTO_ICMP;
+import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.POLLIN;
+import static android.system.OsConstants.SOCK_DGRAM;
import android.annotation.Nullable;
+import android.app.DownloadManager;
+import android.app.DownloadManager.Query;
+import android.app.DownloadManager.Request;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.LinkProperties;
@@ -32,12 +45,13 @@
import android.net.NetworkRequest;
import android.net.Proxy;
import android.net.ProxyInfo;
+import android.net.Uri;
import android.net.VpnService;
import android.net.wifi.WifiManager;
-import android.provider.Settings;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemProperties;
+import android.provider.Settings;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiSelector;
@@ -64,12 +78,12 @@
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
-import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Random;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -1009,6 +1023,9 @@
}
public void testB141603906() throws Exception {
+ if (!supportedHardware()) {
+ return;
+ }
final InetSocketAddress src = new InetSocketAddress(0);
final InetSocketAddress dst = new InetSocketAddress(0);
final int NUM_THREADS = 8;
@@ -1087,4 +1104,62 @@
received = true;
}
}
+
+ /**
+ * Verifies that DownloadManager has CONNECTIVITY_USE_RESTRICTED_NETWORKS permission that can
+ * bind socket to VPN when it is in VPN disallowed list but requested downloading app is in VPN
+ * allowed list.
+ * See b/165774987.
+ */
+ public void testDownloadWithDownloadManagerDisallowed() throws Exception {
+ if (!supportedHardware()) return;
+
+ // Start a VPN with DownloadManager package in disallowed list.
+ startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+ new String[] {"192.0.2.0/24", "2001:db8::/32"},
+ "" /* allowedApps */, "com.android.providers.downloads", null /* proxyInfo */,
+ null /* underlyingNetworks */, false /* isAlwaysMetered */);
+
+ final Context context = VpnTest.this.getInstrumentation().getContext();
+ final DownloadManager dm = context.getSystemService(DownloadManager.class);
+ final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
+ try {
+ context.registerReceiver(receiver,
+ new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+
+ // Enqueue a request and check only one download.
+ final long id = dm.enqueue(new Request(Uri.parse("https://www.google.com")));
+ assertEquals(1, getTotalNumberDownloads(dm, new Query()));
+ assertEquals(1, getTotalNumberDownloads(dm, new Query().setFilterById(id)));
+
+ // Wait for download complete and check status.
+ assertEquals(id, receiver.get(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertEquals(1, getTotalNumberDownloads(dm,
+ new Query().setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL)));
+
+ // Remove download.
+ assertEquals(1, dm.remove(id));
+ assertEquals(0, getTotalNumberDownloads(dm, new Query()));
+ } finally {
+ context.unregisterReceiver(receiver);
+ }
+ }
+
+ private static int getTotalNumberDownloads(final DownloadManager dm, final Query query) {
+ try (Cursor cursor = dm.query(query)) { return cursor.getCount(); }
+ }
+
+ private static class DownloadCompleteReceiver extends BroadcastReceiver {
+ private final CompletableFuture<Long> future = new CompletableFuture<>();
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ future.complete(intent.getLongExtra(
+ DownloadManager.EXTRA_DOWNLOAD_ID, -1 /* defaultValue */));
+ }
+
+ public long get(long timeout, TimeUnit unit) throws Exception {
+ return future.get(timeout, unit);
+ }
+ }
}
diff --git a/tests/cts/hostside/app2/Android.bp b/tests/cts/hostside/app2/Android.bp
index a6e9b11..8e27931 100644
--- a/tests/cts/hostside/app2/Android.bp
+++ b/tests/cts/hostside/app2/Android.bp
@@ -23,7 +23,6 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
certificate: ":cts-net-app",
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index ad270b3..eb777f2 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -16,38 +16,43 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.cts.net.hostside.app2" >
+ package="com.android.cts.net.hostside.app2">
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
- <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.INTERNET"/>
<!--
- 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.
+ 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.
+ 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.
- -->
+ 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">
- <activity android:name=".MyActivity" android:exported="true"/>
- <service android:name=".MyService" android:exported="true"/>
- <service android:name=".MyForegroundService" android:exported="true"/>
- <service android:name=".RemoteSocketFactoryService" android:exported="true"/>
+ <activity android:name=".MyActivity"
+ android:exported="true"/>
+ <service android:name=".MyService"
+ android:exported="true"/>
+ <service android:name=".MyForegroundService"
+ android:exported="true"/>
+ <service android:name=".RemoteSocketFactoryService"
+ android:exported="true"/>
- <receiver android:name=".MyBroadcastReceiver" >
+ <receiver android:name=".MyBroadcastReceiver"
+ android:exported="true">
<intent-filter>
- <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED" />
- <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS" />
- <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS" />
- <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK" />
- <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION" />
- <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST" />
+ <action android:name="android.net.conn.RESTRICT_BACKGROUND_CHANGED"/>
+ <action android:name="com.android.cts.net.hostside.app2.action.GET_COUNTERS"/>
+ <action android:name="com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"/>
+ <action android:name="com.android.cts.net.hostside.app2.action.CHECK_NETWORK"/>
+ <action android:name="com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION"/>
+ <action android:name="com.android.cts.net.hostside.app2.action.SHOW_TOAST"/>
</intent-filter>
</receiver>
</application>
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
index 4598c39..0e25d5e 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideRestrictBackgroundNetworkTests.java
@@ -16,6 +16,8 @@
package com.android.cts.net;
+import android.platform.test.annotations.FlakyTest;
+
import com.android.ddmlib.Log;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -146,6 +148,7 @@
"testBackgroundNetworkAccess_disabled");
}
+ @FlakyTest(bugId=170180675)
public void testAppIdleMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
@@ -176,6 +179,7 @@
"testBackgroundNetworkAccess_disabled");
}
+ @FlakyTest(bugId=170180675)
public void testAppIdleNonMetered_whitelisted() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".AppIdleNonMeteredTest",
"testBackgroundNetworkAccess_whitelisted");
@@ -311,6 +315,14 @@
"testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists");
}
+ /**************************
+ * Restricted mode tests. *
+ **************************/
+ public void testRestrictedMode_networkAccess() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".RestrictedModeTest",
+ "testNetworkAccess");
+ }
+
/*******************
* Helper methods. *
*******************/
@@ -367,7 +379,7 @@
// 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); // Sanity check
+ assertPowerSaveModeWhitelist(packageName, true);
}
protected boolean isDozeModeEnabled() throws Exception {
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
index 62925ad..49b5f9d 100644
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
+++ b/tests/cts/hostside/src/com/android/cts/net/HostsideVpnTests.java
@@ -95,4 +95,9 @@
public void testB141603906() throws Exception {
runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest", "testB141603906");
}
+
+ public void testDownloadWithDownloadManagerDisallowed() throws Exception {
+ runDeviceTests(TEST_PKG, TEST_PKG + ".VpnTest",
+ "testDownloadWithDownloadManagerDisallowed");
+ }
}
diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp
index 3b69602..528171a 100644
--- a/tests/cts/net/Android.bp
+++ b/tests/cts/net/Android.bp
@@ -62,7 +62,6 @@
defaults: ["CtsNetTestCasesDefaults"],
test_suites: [
"cts",
- "vts10",
"general-tests",
],
test_config_template: "AndroidTestTemplate.xml",
diff --git a/tests/cts/net/AndroidTestTemplate.xml b/tests/cts/net/AndroidTestTemplate.xml
index 4e93751..474eefe 100644
--- a/tests/cts/net/AndroidTestTemplate.xml
+++ b/tests/cts/net/AndroidTestTemplate.xml
@@ -20,7 +20,8 @@
<option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
- <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex+com.google.android.tethering.apex" />
+ <option name="config-descriptor:metadata" key="mainline-param" value="CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk+com.google.android.resolv.apex" />
<option name="not-shardable" value="true" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
diff --git a/tests/cts/net/OWNERS b/tests/cts/net/OWNERS
index d558556..432bd9b 100644
--- a/tests/cts/net/OWNERS
+++ b/tests/cts/net/OWNERS
@@ -1,3 +1,3 @@
# Bug component: 31808
-lorenzo@google.com
-satk@google.com
+# Inherits parent owners
+per-file src/android/net/cts/NetworkWatchlistTest.java=alanstokes@google.com
diff --git a/tests/cts/net/TEST_MAPPING b/tests/cts/net/TEST_MAPPING
deleted file mode 100644
index 3162e22..0000000
--- a/tests/cts/net/TEST_MAPPING
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- // TODO: move to mainline-presubmit once supported
- "postsubmit": [
- {
- "name": "CtsNetTestCasesLatestSdk",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- }
- ]
- }
- ],
- "mainline-presubmit": [
- {
- "name": "CtsNetTestCasesLatestSdk[CaptivePortalLoginGoogle.apk+NetworkStackGoogle.apk]",
- "options": [
- {
- "exclude-annotation": "com.android.testutils.SkipPresubmit"
- }
- ]
- }
- ]
-}
diff --git a/tests/cts/net/api23Test/Android.bp b/tests/cts/net/api23Test/Android.bp
index 0ce9826..e43a5e8 100644
--- a/tests/cts/net/api23Test/Android.bp
+++ b/tests/cts/net/api23Test/Android.bp
@@ -45,7 +45,6 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
diff --git a/tests/cts/net/api23Test/AndroidManifest.xml b/tests/cts/net/api23Test/AndroidManifest.xml
index 4889660..69ee0dd 100644
--- a/tests/cts/net/api23Test/AndroidManifest.xml
+++ b/tests/cts/net/api23Test/AndroidManifest.xml
@@ -16,7 +16,7 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.cts.api23test">
+ package="android.net.cts.api23test">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
@@ -26,20 +26,20 @@
<uses-permission android:name="android.permission.INTERNET" />
<application android:usesCleartextTraffic="true">
- <uses-library android:name="android.test.runner" />
+ <uses-library android:name="android.test.runner"/>
- <receiver android:name=".ConnectivityReceiver">
+ <receiver android:name=".ConnectivityReceiver"
+ android:exported="true">
<intent-filter>
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.net.cts.api23test"
- android:label="CTS tests of android.net">
+ android:targetPackage="android.net.cts.api23test"
+ android:label="CTS tests of android.net">
<meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
+ android:value="com.android.cts.runner.CtsTestRunListener"/>
</instrumentation>
</manifest>
-
diff --git a/tests/cts/net/appForApi23/Android.bp b/tests/cts/net/appForApi23/Android.bp
index 399c199..cec6d7f 100644
--- a/tests/cts/net/appForApi23/Android.bp
+++ b/tests/cts/net/appForApi23/Android.bp
@@ -26,7 +26,6 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
"general-tests",
],
diff --git a/tests/cts/net/appForApi23/AndroidManifest.xml b/tests/cts/net/appForApi23/AndroidManifest.xml
index ed4cedb..158b9c4 100644
--- a/tests/cts/net/appForApi23/AndroidManifest.xml
+++ b/tests/cts/net/appForApi23/AndroidManifest.xml
@@ -16,32 +16,32 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.net.cts.appForApi23">
+ package="android.net.cts.appForApi23">
- <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
- <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+ <uses-permission android:name="android.permission.INTERNET"/>
<application>
- <receiver android:name=".ConnectivityReceiver">
+ <receiver android:name=".ConnectivityReceiver"
+ android:exported="true">
<intent-filter>
- <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
+ <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
<intent-filter>
- <action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount" />
+ <action android:name="android.net.cts.appForApi23.getWifiConnectivityActionCount"/>
</intent-filter>
</receiver>
<activity android:name=".ConnectivityListeningActivity"
- android:label="ConnectivityListeningActivity"
- android:exported="true">
+ android:label="ConnectivityListeningActivity"
+ android:exported="true">
<intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
-
diff --git a/tests/cts/net/native/dns/Android.bp b/tests/cts/net/native/dns/Android.bp
index 1704a2b..6defd35 100644
--- a/tests/cts/net/native/dns/Android.bp
+++ b/tests/cts/net/native/dns/Android.bp
@@ -35,6 +35,7 @@
},
test_suites: [
"cts",
+ "general-tests",
"mts",
],
}
diff --git a/tests/cts/net/native/qtaguid/Android.bp b/tests/cts/net/native/qtaguid/Android.bp
index 23a0cf7..4861651 100644
--- a/tests/cts/net/native/qtaguid/Android.bp
+++ b/tests/cts/net/native/qtaguid/Android.bp
@@ -42,7 +42,7 @@
// Tag this module as a cts test artifact
test_suites: [
"cts",
- "vts10",
+ "general-tests",
],
cflags: [
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
index 54509cd..ccbdbd3 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -129,6 +129,12 @@
private static final IBinder BINDER = new Binder();
+ // Lock for accessing Shell Permissions. Use of this lock around adoptShellPermissionIdentity,
+ // runWithShellPermissionIdentity, and callWithShellPermissionIdentity ensures Shell Permission
+ // is not interrupted by another operation (which would drop all previously adopted
+ // permissions).
+ private Object mShellPermissionsIdentityLock = new Object();
+
private Context mContext;
private ConnectivityManager mConnectivityManager;
private ConnectivityDiagnosticsManager mCdm;
@@ -244,20 +250,24 @@
CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY,
new String[] {getCertHashForThisPackage()});
- runWithShellPermissionIdentity(
- () -> {
- mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
- mCarrierConfigManager.notifyConfigChangedForSubId(subId);
- },
- android.Manifest.permission.MODIFY_PHONE_STATE);
+ synchronized (mShellPermissionsIdentityLock) {
+ runWithShellPermissionIdentity(
+ () -> {
+ mCarrierConfigManager.overrideConfig(subId, carrierConfigs);
+ mCarrierConfigManager.notifyConfigChangedForSubId(subId);
+ },
+ android.Manifest.permission.MODIFY_PHONE_STATE);
+ }
// TODO(b/157779832): This should use android.permission.CHANGE_NETWORK_STATE. However, the
// shell does not have CHANGE_NETWORK_STATE, so use CONNECTIVITY_INTERNAL until the shell
// permissions are updated.
- runWithShellPermissionIdentity(
- () -> mConnectivityManager.requestNetwork(
- CELLULAR_NETWORK_REQUEST, testNetworkCallback),
- android.Manifest.permission.CONNECTIVITY_INTERNAL);
+ synchronized (mShellPermissionsIdentityLock) {
+ runWithShellPermissionIdentity(
+ () -> mConnectivityManager.requestNetwork(
+ CELLULAR_NETWORK_REQUEST, testNetworkCallback),
+ android.Manifest.permission.CONNECTIVITY_INTERNAL);
+ }
final Network network = testNetworkCallback.waitForAvailable();
assertNotNull(network);
@@ -536,9 +546,18 @@
}
private class CarrierConfigReceiver extends BroadcastReceiver {
+ // CountDownLatch used to wait for this BroadcastReceiver to be notified of a CarrierConfig
+ // change. This latch will be counted down if a broadcast indicates this package has carrier
+ // configs, or if an Exception occurs in #onReceive.
private final CountDownLatch mLatch = new CountDownLatch(1);
private final int mSubId;
+ // #onReceive may encounter Exceptions while running on the Process' main Thread and
+ // #waitForCarrierConfigChanged checks the cached Exception from the test Thread. These
+ // Exceptions must be cached and thrown later, as throwing on the Process' main Thread will
+ // crash the process and cause other tests to fail.
+ private Exception mOnReceiveException;
+
CarrierConfigReceiver(int subId) {
mSubId = subId;
}
@@ -546,6 +565,7 @@
@Override
public void onReceive(Context context, Intent intent) {
if (!CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+ // Received an incorrect broadcast - ignore
return;
}
@@ -553,24 +573,64 @@
intent.getIntExtra(
CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
- if (mSubId != subId) return;
+ if (mSubId != subId) {
+ // Received a broadcast for the wrong subId - ignore
+ return;
+ }
- final PersistableBundle carrierConfigs = mCarrierConfigManager.getConfigForSubId(subId);
- if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) return;
+ final PersistableBundle carrierConfigs;
+ try {
+ synchronized (mShellPermissionsIdentityLock) {
+ carrierConfigs = callWithShellPermissionIdentity(
+ () -> mCarrierConfigManager.getConfigForSubId(subId),
+ android.Manifest.permission.READ_PHONE_STATE);
+ }
+ } catch (Exception exception) {
+ // callWithShellPermissionIdentity() threw an Exception - cache it and allow
+ // waitForCarrierConfigChanged() to throw it
+ mOnReceiveException = exception;
+ mLatch.countDown();
+ return;
+ }
- final String[] certs =
- carrierConfigs.getStringArray(
- CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
+ if (!CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfigs)) {
+ // Configs are not for an identified carrier (meaning they are defaults) - ignore
+ return;
+ }
+
+ final String[] certs = carrierConfigs.getStringArray(
+ CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY);
try {
if (ArrayUtils.contains(certs, getCertHashForThisPackage())) {
+ // Received an update for this package's cert hash - countdown and exit
mLatch.countDown();
}
- } catch (Exception e) {
+ // Broadcast is for the right subId, but does not show this package as Carrier
+ // Privileged. Keep waiting for a broadcast that indicates Carrier Privileges.
+ } catch (Exception exception) {
+ // getCertHashForThisPackage() threw an Exception - cache it and allow
+ // waitForCarrierConfigChanged() to throw it
+ mOnReceiveException = exception;
+ mLatch.countDown();
}
}
+ /**
+ * Waits for the CarrierConfig changed broadcast to reach this CarrierConfigReceiver.
+ *
+ * <p>Must be called from the Test Thread.
+ *
+ * @throws Exception if an Exception occurred during any #onReceive invocation
+ */
boolean waitForCarrierConfigChanged() throws Exception {
- return mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT, TimeUnit.MILLISECONDS);
+ final boolean result = mLatch.await(CARRIER_CONFIG_CHANGED_BROADCAST_TIMEOUT,
+ TimeUnit.MILLISECONDS);
+
+ if (mOnReceiveException != null) {
+ throw mOnReceiveException;
+ }
+
+ return result;
}
}
}
diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
index b7cc95d..cbf43e7 100644
--- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -16,7 +16,9 @@
package android.net.cts;
+import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
+import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.content.pm.PackageManager.FEATURE_ETHERNET;
import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
import static android.content.pm.PackageManager.FEATURE_USB_HOST;
@@ -41,6 +43,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.testutils.TestPermissionUtil.runAsShell;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -55,6 +58,7 @@
import android.app.Instrumentation;
import android.app.PendingIntent;
import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -96,10 +100,14 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.ArrayUtils;
+import com.android.testutils.RecorderCallback.CallbackEntry;
import com.android.testutils.SkipPresubmit;
+import com.android.testutils.TestableNetworkCallback;
import libcore.io.Streams;
+import junit.framework.AssertionFailedError;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -115,6 +123,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
@@ -122,6 +131,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
@@ -152,6 +162,9 @@
// device could have only one interface: data, wifi.
private static final int MIN_NUM_NETWORK_TYPES = 1;
+ // Airplane Mode BroadcastReceiver Timeout
+ private static final long AIRPLANE_MODE_CHANGE_TIMEOUT_MS = 10_000L;
+
// Minimum supported keepalive counts for wifi and cellular.
public static final int MIN_SUPPORTED_CELLULAR_KEEPALIVE_COUNT = 1;
public static final int MIN_SUPPORTED_WIFI_KEEPALIVE_COUNT = 3;
@@ -199,6 +212,8 @@
} catch (Exception e) {}
}
mUiAutomation = mInstrumentation.getUiAutomation();
+
+ assertNotNull("CTS requires a working Internet connection", mCm.getActiveNetwork());
}
@After
@@ -357,8 +372,8 @@
wifiAddressString, wifiNetwork, cellNetwork),
wifiAddressString.equals(cellAddressString));
- // Sanity check that the IP addresses that the requests appeared to come from
- // are actually on the respective networks.
+ // Verify that the IP addresses that the requests appeared to come from are actually on the
+ // respective networks.
assertOnNetwork(wifiAddressString, wifiNetwork);
assertOnNetwork(cellAddressString, cellNetwork);
@@ -476,6 +491,12 @@
.build();
}
+ private NetworkRequest makeCellNetworkRequest() {
+ return new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .build();
+ }
+
/**
* Exercises both registerNetworkCallback and unregisterNetworkCallback. This checks to
* see if we get a callback for the TRANSPORT_WIFI transport type being available.
@@ -714,7 +735,7 @@
private void assertMultipathPreferenceIsEventually(Network network, int oldValue,
int expectedValue) {
- // Sanity check : if oldValue == expectedValue, there is no way to guarantee the test
+ // Quick check : if oldValue == expectedValue, there is no way to guarantee the test
// is not flaky.
assertNotSame(oldValue, expectedValue);
@@ -1018,7 +1039,7 @@
// NAT-T keepalive. If keepalive limits from resource overlay is not zero, TCP keepalive
// needs to be supported except if the kernel doesn't support it.
if (!isTcpKeepaliveSupportedByKernel()) {
- // Sanity check to ensure the callback result is expected.
+ // Verify that the callback result is expected.
runWithShellPermissionIdentity(() -> {
assertEquals(0, createConcurrentSocketKeepalives(network, srcAddr, 0, 1));
});
@@ -1381,4 +1402,133 @@
}
}
}
+
+ /**
+ * Verifies that apps are allowed to call setAirplaneMode if they declare
+ * NETWORK_AIRPLANE_MODE permission in their manifests.
+ * See b/145164696.
+ */
+ @AppModeFull(reason = "NETWORK_AIRPLANE_MODE permission can't be granted to instant apps")
+ @Test
+ public void testSetAirplaneMode() throws Exception{
+ final boolean supportWifi = mPackageManager.hasSystemFeature(FEATURE_WIFI);
+ final boolean supportTelephony = mPackageManager.hasSystemFeature(FEATURE_TELEPHONY);
+ // store the current state of airplane mode
+ final boolean isAirplaneModeEnabled = isAirplaneModeEnabled();
+ final TestableNetworkCallback wifiCb = new TestableNetworkCallback();
+ final TestableNetworkCallback telephonyCb = new TestableNetworkCallback();
+ // disable airplane mode to reach a known state
+ runShellCommand("cmd connectivity airplane-mode disable");
+ // Verify that networks are available as expected if wifi or cell is supported. Continue the
+ // test if none of them are supported since test should still able to verify the permission
+ // mechanism.
+ if (supportWifi) requestAndWaitForAvailable(makeWifiNetworkRequest(), wifiCb);
+ if (supportTelephony) requestAndWaitForAvailable(makeCellNetworkRequest(), telephonyCb);
+
+ try {
+ // Verify we cannot set Airplane Mode without correct permission:
+ try {
+ setAndVerifyAirplaneMode(true);
+ fail("SecurityException should have been thrown when setAirplaneMode was called"
+ + "without holding permission NETWORK_AIRPLANE_MODE.");
+ } catch (SecurityException expected) {}
+
+ // disable airplane mode again to reach a known state
+ runShellCommand("cmd connectivity airplane-mode disable");
+
+ // adopt shell permission which holds NETWORK_AIRPLANE_MODE
+ mUiAutomation.adoptShellPermissionIdentity();
+
+ // Verify we can enable Airplane Mode with correct permission:
+ try {
+ setAndVerifyAirplaneMode(true);
+ } catch (SecurityException e) {
+ fail("SecurityException should not have been thrown when setAirplaneMode(true) was"
+ + "called whilst holding the NETWORK_AIRPLANE_MODE permission.");
+ }
+ // Verify that the enabling airplane mode takes effect as expected to prevent flakiness
+ // caused by fast airplane mode switches. Ensure network lost before turning off
+ // airplane mode.
+ if (supportWifi) waitForLost(wifiCb);
+ if (supportTelephony) waitForLost(telephonyCb);
+
+ // Verify we can disable Airplane Mode with correct permission:
+ try {
+ setAndVerifyAirplaneMode(false);
+ } catch (SecurityException e) {
+ fail("SecurityException should not have been thrown when setAirplaneMode(false) was"
+ + "called whilst holding the NETWORK_AIRPLANE_MODE permission.");
+ }
+ // Verify that turning airplane mode off takes effect as expected.
+ if (supportWifi) waitForAvailable(wifiCb);
+ if (supportTelephony) waitForAvailable(telephonyCb);
+ } finally {
+ if (supportWifi) mCm.unregisterNetworkCallback(wifiCb);
+ if (supportTelephony) mCm.unregisterNetworkCallback(telephonyCb);
+ // Restore the previous state of airplane mode and permissions:
+ runShellCommand("cmd connectivity airplane-mode "
+ + (isAirplaneModeEnabled ? "enable" : "disable"));
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
+ private void requestAndWaitForAvailable(@NonNull final NetworkRequest request,
+ @NonNull final TestableNetworkCallback cb) {
+ mCm.registerNetworkCallback(request, cb);
+ waitForAvailable(cb);
+ }
+
+ private void waitForAvailable(@NonNull final TestableNetworkCallback cb) {
+ cb.eventuallyExpect(CallbackEntry.AVAILABLE, AIRPLANE_MODE_CHANGE_TIMEOUT_MS,
+ c -> c instanceof CallbackEntry.Available);
+ }
+
+ private void waitForLost(@NonNull final TestableNetworkCallback cb) {
+ cb.eventuallyExpect(CallbackEntry.LOST, AIRPLANE_MODE_CHANGE_TIMEOUT_MS,
+ c -> c instanceof CallbackEntry.Lost);
+ }
+
+ private void setAndVerifyAirplaneMode(Boolean expectedResult)
+ throws Exception {
+ final CompletableFuture<Boolean> actualResult = new CompletableFuture();
+ BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // The defaultValue of getExtraBoolean should be the opposite of what is
+ // expected, thus ensuring a test failure if the extra is absent.
+ actualResult.complete(intent.getBooleanExtra("state", !expectedResult));
+ }
+ };
+ try {
+ mContext.registerReceiver(receiver,
+ new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
+ mCm.setAirplaneMode(expectedResult);
+ final String msg = "Setting Airplane Mode failed,";
+ assertEquals(msg, expectedResult, actualResult.get(AIRPLANE_MODE_CHANGE_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS));
+ } finally {
+ mContext.unregisterReceiver(receiver);
+ }
+ }
+
+ private static boolean isAirplaneModeEnabled() {
+ return runShellCommand("cmd connectivity airplane-mode")
+ .trim().equals("enabled");
+ }
+
+ @Test
+ public void testGetCaptivePortalServerUrl() {
+ final String permission = Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q
+ ? CONNECTIVITY_INTERNAL
+ : NETWORK_SETTINGS;
+ final String url = runAsShell(permission, mCm::getCaptivePortalServerUrl);
+ assertNotNull("getCaptivePortalServerUrl must not be null", url);
+ try {
+ final URL parsedUrl = new URL(url);
+ // As per the javadoc, the URL must be HTTP
+ assertEquals("Invalid captive portal URL protocol", "http", parsedUrl.getProtocol());
+ } catch (MalformedURLException e) {
+ throw new AssertionFailedError("Captive portal server URL is invalid: " + e);
+ }
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
index 6d3db89..691ab99 100644
--- a/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
+++ b/tests/cts/net/src/android/net/cts/MultinetworkApiTest.java
@@ -103,6 +103,7 @@
}
}
+ @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testSetprocnetwork() throws ErrnoException {
// Hopefully no prior test in this process space has set a default network.
assertNull(mCM.getProcessDefaultNetwork());
@@ -145,6 +146,7 @@
}
}
+ @AppModeFull(reason = "CHANGE_NETWORK_STATE permission can't be granted to instant apps")
public void testSetsocknetwork() throws ErrnoException {
for (Network network : getTestableNetworks()) {
int errno = runSetsocknetwork(network.getNetworkHandle());
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index d2ca3f8..69d90aa 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -18,24 +18,32 @@
import android.app.Instrumentation
import android.content.Context
import android.net.ConnectivityManager
+import android.net.InetAddresses
+import android.net.IpPrefix
import android.net.KeepalivePacketData
import android.net.LinkAddress
import android.net.LinkProperties
+import android.net.NattKeepalivePacketData
import android.net.Network
import android.net.NetworkAgent
-import android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER
-import android.net.NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT
-import android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER
-import android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS
-import android.net.NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED
-import android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE
-import android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE
import android.net.NetworkAgent.INVALID_NETWORK
import android.net.NetworkAgent.VALID_NETWORK
import android.net.NetworkAgentConfig
import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
+import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
+import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
+import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
+import android.net.NetworkCapabilities.TRANSPORT_TEST
+import android.net.NetworkCapabilities.TRANSPORT_VPN
+import android.net.NetworkInfo
import android.net.NetworkProvider
import android.net.NetworkRequest
+import android.net.RouteInfo
import android.net.SocketKeepalive
import android.net.StringNetworkSpecifier
import android.net.Uri
@@ -50,29 +58,36 @@
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
import android.os.Build
-import android.os.Bundle
-import android.os.Handler
import android.os.HandlerThread
import android.os.Looper
import android.os.Message
-import android.os.Messenger
+import android.util.DebugUtils.valueToString
import androidx.test.InstrumentationRegistry
import androidx.test.runner.AndroidJUnit4
-import com.android.internal.util.AsyncChannel
+import com.android.connectivity.aidl.INetworkAgent
+import com.android.connectivity.aidl.INetworkAgentRegistry
import com.android.net.module.util.ArrayTrackRecord
import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.Lost
import com.android.testutils.TestableNetworkCallback
import org.junit.After
import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import java.net.InetAddress
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.argThat
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
import java.time.Duration
+import java.util.Arrays
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@@ -80,6 +95,7 @@
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertTrue
+import kotlin.test.fail
// This test doesn't really have a constraint on how fast the methods should return. If it's
// going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
@@ -98,7 +114,7 @@
private const val FAKE_NET_ID = 1098
private val instrumentation: Instrumentation
get() = InstrumentationRegistry.getInstrumentation()
-private val context: Context
+private val realContext: Context
get() = InstrumentationRegistry.getContext()
private fun Message(what: Int, arg1: Int, arg2: Int, obj: Any?) = Message.obtain().also {
it.what = what
@@ -110,14 +126,14 @@
@RunWith(AndroidJUnit4::class)
class NetworkAgentTest {
@Rule @JvmField
- val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+ val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.R)
- private val LOCAL_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.1")
- private val REMOTE_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.2")
+ private val LOCAL_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.1")
+ private val REMOTE_IPV4_ADDRESS = InetAddresses.parseNumericAddress("192.0.2.2")
- private val mCM = context.getSystemService(ConnectivityManager::class.java)
+ private val mCM = realContext.getSystemService(ConnectivityManager::class.java)
private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
- private val mFakeConnectivityService by lazy { FakeConnectivityService(mHandlerThread.looper) }
+ private val mFakeConnectivityService = FakeConnectivityService()
private class Provider(context: Context, looper: Looper) :
NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
@@ -144,42 +160,34 @@
* This fake only supports speaking to one harnessed agent at a time because it
* only keeps track of one async channel.
*/
- private class FakeConnectivityService(looper: Looper) {
- private val CMD_EXPECT_DISCONNECT = 1
- private var disconnectExpected = false
- private val msgHistory = ArrayTrackRecord<Message>().newReadHead()
- private val asyncChannel = AsyncChannel()
- private val handler = object : Handler(looper) {
- override fun handleMessage(msg: Message) {
- msgHistory.add(Message.obtain(msg)) // make a copy as the original will be recycled
- when (msg.what) {
- CMD_EXPECT_DISCONNECT -> disconnectExpected = true
- AsyncChannel.CMD_CHANNEL_HALF_CONNECTED ->
- asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)
- AsyncChannel.CMD_CHANNEL_DISCONNECTED ->
- if (!disconnectExpected) {
- fail("Agent unexpectedly disconnected")
- } else {
- disconnectExpected = false
- }
- }
- }
+ private class FakeConnectivityService {
+ val mockRegistry = mock(INetworkAgentRegistry::class.java)
+ private var agentField: INetworkAgent? = null
+ private val registry = object : INetworkAgentRegistry.Stub(),
+ INetworkAgentRegistry by mockRegistry {
+ // asBinder has implementations in both INetworkAgentRegistry.Stub and mockRegistry, so
+ // it needs to be disambiguated. Just fail the test as it should be unused here.
+ // asBinder is used when sending the registry in binder transactions, so not in this
+ // test (the test just uses in-process direct calls). If it were used across processes,
+ // using the Stub super.asBinder() implementation would allow sending the registry in
+ // binder transactions, while recording incoming calls on the other mockito-generated
+ // methods.
+ override fun asBinder() = fail("asBinder should be unused in this test")
}
- fun connect(agentMsngr: Messenger) = asyncChannel.connect(context, handler, agentMsngr)
+ val agent: INetworkAgent
+ get() = agentField ?: fail("No INetworkAgent")
- fun disconnect() = asyncChannel.disconnect()
+ fun connect(agent: INetworkAgent) {
+ this.agentField = agent
+ agent.onRegistered(registry)
+ }
- fun sendMessage(what: Int, arg1: Int = 0, arg2: Int = 0, obj: Any? = null) =
- asyncChannel.sendMessage(Message(what, arg1, arg2, obj))
-
- fun expectMessage(what: Int) =
- assertNotNull(msgHistory.poll(DEFAULT_TIMEOUT_MS) { it.what == what })
-
- fun willExpectDisconnectOnce() = handler.sendEmptyMessage(CMD_EXPECT_DISCONNECT)
+ fun disconnect() = agent.onDisconnected()
}
private open class TestableNetworkAgent(
+ context: Context,
looper: Looper,
val nc: NetworkCapabilities,
val lp: LinkProperties,
@@ -300,36 +308,42 @@
callbacksToCleanUp.add(callback)
}
- private fun createNetworkAgent(name: String? = null): TestableNetworkAgent {
- val nc = NetworkCapabilities().apply {
- addTransportType(NetworkCapabilities.TRANSPORT_TEST)
- removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
- removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
- addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
- addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
- addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ private fun createNetworkAgent(
+ context: Context = realContext,
+ name: String? = null,
+ initialNc: NetworkCapabilities? = null,
+ initialLp: LinkProperties? = null
+ ): TestableNetworkAgent {
+ val nc = initialNc ?: NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ removeCapability(NET_CAPABILITY_TRUSTED)
+ removeCapability(NET_CAPABILITY_INTERNET)
+ addCapability(NET_CAPABILITY_NOT_SUSPENDED)
+ addCapability(NET_CAPABILITY_NOT_ROAMING)
+ addCapability(NET_CAPABILITY_NOT_VPN)
if (null != name) {
setNetworkSpecifier(StringNetworkSpecifier(name))
}
}
- val lp = LinkProperties().apply {
- addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 0))
+ val lp = initialLp ?: LinkProperties().apply {
+ addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 32))
+ addRoute(RouteInfo(IpPrefix("0.0.0.0/0"), null, null))
}
val config = NetworkAgentConfig.Builder().build()
- return TestableNetworkAgent(mHandlerThread.looper, nc, lp, config).also {
+ return TestableNetworkAgent(context, mHandlerThread.looper, nc, lp, config).also {
agentsToCleanUp.add(it)
}
}
- private fun createConnectedNetworkAgent(name: String? = null):
+ private fun createConnectedNetworkAgent(context: Context = realContext, name: String? = null):
Pair<TestableNetworkAgent, TestableNetworkCallback> {
val request: NetworkRequest = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.build()
val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
requestNetwork(request, callback)
- val agent = createNetworkAgent(name)
+ val agent = createNetworkAgent(context, name)
agent.register()
agent.markConnected()
return agent to callback
@@ -370,7 +384,7 @@
val callbacks = thresholds.map { strength ->
val request = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.setSignalStrength(strength)
.build()
TestableNetworkCallback(DEFAULT_TIMEOUT_MS).also {
@@ -415,17 +429,15 @@
@Test
fun testSocketKeepalive(): Unit = createNetworkAgentWithFakeCS().let { agent ->
- val packet = object : KeepalivePacketData(
+ val packet = NattKeepalivePacketData(
LOCAL_IPV4_ADDRESS /* srcAddress */, 1234 /* srcPort */,
REMOTE_IPV4_ADDRESS /* dstAddress */, 4567 /* dstPort */,
- ByteArray(100 /* size */) { it.toByte() /* init */ }) {}
+ ByteArray(100 /* size */))
val slot = 4
val interval = 37
- mFakeConnectivityService.sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
- arg1 = slot, obj = packet)
- mFakeConnectivityService.sendMessage(CMD_START_SOCKET_KEEPALIVE,
- arg1 = slot, arg2 = interval, obj = packet)
+ mFakeConnectivityService.agent.onAddNattKeepalivePacketFilter(slot, packet)
+ mFakeConnectivityService.agent.onStartNattSocketKeepalive(slot, interval, packet)
agent.expectCallback<OnAddKeepalivePacketFilter>().let {
assertEquals(it.slot, slot)
@@ -442,13 +454,11 @@
// Check that when the agent sends a keepalive event, ConnectivityService receives the
// expected message.
agent.sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED)
- mFakeConnectivityService.expectMessage(NetworkAgent.EVENT_SOCKET_KEEPALIVE).let() {
- assertEquals(slot, it.arg1)
- assertEquals(SocketKeepalive.ERROR_UNSUPPORTED, it.arg2)
- }
+ verify(mFakeConnectivityService.mockRegistry, timeout(DEFAULT_TIMEOUT_MS))
+ .sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED)
- mFakeConnectivityService.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, arg1 = slot)
- mFakeConnectivityService.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, arg1 = slot)
+ mFakeConnectivityService.agent.onStopSocketKeepalive(slot)
+ mFakeConnectivityService.agent.onRemoveKeepalivePacketFilter(slot)
agent.expectCallback<OnStopSocketKeepalive>().let {
assertEquals(it.slot, slot)
}
@@ -470,10 +480,10 @@
it.getInterfaceName() == ifaceName
}
val nc = NetworkCapabilities(agent.nc)
- nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ nc.addCapability(NET_CAPABILITY_NOT_METERED)
agent.sendNetworkCapabilities(nc)
callback.expectCapabilitiesThat(agent.network) {
- it.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+ it.hasCapability(NET_CAPABILITY_NOT_METERED)
}
}
@@ -487,12 +497,12 @@
val name2 = UUID.randomUUID().toString()
val request1 = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(StringNetworkSpecifier(name1))
.build()
val request2 = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.setNetworkSpecifier(StringNetworkSpecifier(name2))
.build()
val callback1 = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
@@ -503,19 +513,19 @@
// Then file the interesting request
val request = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.build()
val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
requestNetwork(request, callback)
// Connect the first Network
- createConnectedNetworkAgent(name1).let { (agent1, _) ->
+ createConnectedNetworkAgent(name = name1).let { (agent1, _) ->
callback.expectAvailableThenValidatedCallbacks(agent1.network)
// Upgrade agent1 to a better score so that there is no ambiguity when
// agent2 connects that agent1 is still better
agent1.sendNetworkScore(BETTER_NETWORK_SCORE - 1)
// Connect the second agent
- createConnectedNetworkAgent(name2).let { (agent2, _) ->
+ createConnectedNetworkAgent(name = name2).let { (agent2, _) ->
agent2.markConnected()
// The callback should not see anything yet
callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
@@ -528,10 +538,100 @@
// tearDown() will unregister the requests and agents
}
+ private fun hasAllTransports(nc: NetworkCapabilities?, transports: IntArray) =
+ nc != null && transports.all { nc.hasTransport(it) }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ fun testSetUnderlyingNetworks() {
+ val request = NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_VPN)
+ .removeCapability(NET_CAPABILITY_NOT_VPN)
+ .removeCapability(NET_CAPABILITY_TRUSTED) // TODO: add to VPN!
+ .build()
+ val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+ registerNetworkCallback(request, callback)
+
+ val nc = NetworkCapabilities().apply {
+ addTransportType(TRANSPORT_TEST)
+ addTransportType(TRANSPORT_VPN)
+ removeCapability(NET_CAPABILITY_NOT_VPN)
+ }
+ val defaultNetwork = mCM.activeNetwork
+ assertNotNull(defaultNetwork)
+ val defaultNetworkCapabilities = mCM.getNetworkCapabilities(defaultNetwork)
+ val defaultNetworkTransports = defaultNetworkCapabilities.transportTypes
+
+ val agent = createNetworkAgent(initialNc = nc)
+ agent.register()
+ agent.markConnected()
+ callback.expectAvailableThenValidatedCallbacks(agent.network!!)
+
+ // Check that the default network's transport is propagated to the VPN.
+ var vpnNc = mCM.getNetworkCapabilities(agent.network)
+ assertNotNull(vpnNc)
+
+ val testAndVpn = intArrayOf(TRANSPORT_TEST, TRANSPORT_VPN)
+ assertTrue(hasAllTransports(vpnNc, testAndVpn))
+ assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_VPN))
+ assertTrue(hasAllTransports(vpnNc, defaultNetworkTransports),
+ "VPN transports ${Arrays.toString(vpnNc.transportTypes)}" +
+ " lacking transports from ${Arrays.toString(defaultNetworkTransports)}")
+
+ // Check that when no underlying networks are announced the underlying transport disappears.
+ agent.setUnderlyingNetworks(listOf<Network>())
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.transportTypes.size == 2 && hasAllTransports(it, testAndVpn)
+ }
+
+ // Put the underlying network back and check that the underlying transport reappears.
+ val expectedTransports = (defaultNetworkTransports.toSet() + TRANSPORT_TEST + TRANSPORT_VPN)
+ .toIntArray()
+ agent.setUnderlyingNetworks(null)
+ callback.expectCapabilitiesThat(agent.network!!) {
+ it.transportTypes.size == expectedTransports.size &&
+ hasAllTransports(it, expectedTransports)
+ }
+
+ // Check that some underlying capabilities are propagated.
+ // This is not very accurate because the test does not control the capabilities of the
+ // underlying networks, and because not congested, not roaming, and not suspended are the
+ // default anyway. It's still useful as an extra check though.
+ vpnNc = mCM.getNetworkCapabilities(agent.network)
+ for (cap in listOf(NET_CAPABILITY_NOT_CONGESTED,
+ NET_CAPABILITY_NOT_ROAMING,
+ NET_CAPABILITY_NOT_SUSPENDED)) {
+ val capStr = valueToString(NetworkCapabilities::class.java, "NET_CAPABILITY_", cap)
+ if (defaultNetworkCapabilities.hasCapability(cap) && !vpnNc.hasCapability(cap)) {
+ fail("$capStr not propagated from underlying: $defaultNetworkCapabilities")
+ }
+ }
+
+ agent.unregister()
+ callback.expectCallback<Lost>(agent.network)
+ }
+
+ @Test
+ @IgnoreUpTo(Build.VERSION_CODES.R)
+ fun testAgentStartsInConnecting() {
+ val mockContext = mock(Context::class.java)
+ val mockCm = mock(ConnectivityManager::class.java)
+ doReturn(mockCm).`when`(mockContext).getSystemService(Context.CONNECTIVITY_SERVICE)
+ createConnectedNetworkAgent(mockContext)
+ verify(mockCm).registerNetworkAgent(any(),
+ argThat<NetworkInfo> { it.detailedState == NetworkInfo.DetailedState.CONNECTING },
+ any(LinkProperties::class.java),
+ any(NetworkCapabilities::class.java),
+ anyInt() /* score */,
+ any(NetworkAgentConfig::class.java),
+ eq(NetworkProvider.ID_NONE))
+ }
+
@Test
fun testSetAcceptUnvalidated() {
createNetworkAgentWithFakeCS().let { agent ->
- mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 1)
+ mFakeConnectivityService.agent.onSaveAcceptUnvalidated(true)
agent.expectCallback<OnSaveAcceptUnvalidated>().let {
assertTrue(it.accept)
}
@@ -542,19 +642,18 @@
@Test
fun testSetAcceptUnvalidatedPreventAutomaticReconnect() {
createNetworkAgentWithFakeCS().let { agent ->
- mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 0)
- mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+ mFakeConnectivityService.agent.onSaveAcceptUnvalidated(false)
+ mFakeConnectivityService.agent.onPreventAutomaticReconnect()
agent.expectCallback<OnSaveAcceptUnvalidated>().let {
assertFalse(it.accept)
}
agent.expectCallback<OnAutomaticReconnectDisabled>()
agent.assertNoCallback()
// When automatic reconnect is turned off, the network is torn down and
- // ConnectivityService sends a disconnect. This in turn causes the agent
- // to send a DISCONNECTED message to CS.
- mFakeConnectivityService.willExpectDisconnectOnce()
+ // ConnectivityService disconnects. As part of the disconnect, ConnectivityService will
+ // also send itself a message to unregister the NetworkAgent from its internal
+ // structure.
mFakeConnectivityService.disconnect()
- mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
agent.expectCallback<OnNetworkUnwanted>()
}
}
@@ -562,12 +661,10 @@
@Test
fun testPreventAutomaticReconnect() {
createNetworkAgentWithFakeCS().let { agent ->
- mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+ mFakeConnectivityService.agent.onPreventAutomaticReconnect()
agent.expectCallback<OnAutomaticReconnectDisabled>()
agent.assertNoCallback()
- mFakeConnectivityService.willExpectDisconnectOnce()
mFakeConnectivityService.disconnect()
- mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
agent.expectCallback<OnNetworkUnwanted>()
}
}
@@ -575,18 +672,14 @@
@Test
fun testValidationStatus() = createNetworkAgentWithFakeCS().let { agent ->
val uri = Uri.parse("http://www.google.com")
- val bundle = Bundle().apply {
- putString(NetworkAgent.REDIRECT_URL_KEY, uri.toString())
- }
- mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
- arg1 = VALID_NETWORK, obj = bundle)
+ mFakeConnectivityService.agent.onValidationStatusChanged(VALID_NETWORK,
+ uri.toString())
agent.expectCallback<OnValidationStatus>().let {
assertEquals(it.status, VALID_NETWORK)
assertEquals(it.uri, uri)
}
- mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
- arg1 = INVALID_NETWORK, obj = Bundle())
+ mFakeConnectivityService.agent.onValidationStatusChanged(INVALID_NETWORK, null)
agent.expectCallback<OnValidationStatus>().let {
assertEquals(it.status, INVALID_NETWORK)
assertNull(it.uri)
@@ -600,7 +693,7 @@
// First create a request to make sure the network is kept up
val request1 = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.build()
val callback1 = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS).also {
registerNetworkCallback(request1, it)
@@ -610,7 +703,7 @@
// Then file the interesting request
val request = NetworkRequest.Builder()
.clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .addTransportType(TRANSPORT_TEST)
.build()
val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
requestNetwork(request, callback)
@@ -621,18 +714,18 @@
// Send TEMP_NOT_METERED and check that the callback is called appropriately.
val nc1 = NetworkCapabilities(agent.nc)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
agent.sendNetworkCapabilities(nc1)
callback.expectCapabilitiesThat(agent.network) {
- it.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
}
// Remove TEMP_NOT_METERED and check that the callback is called appropriately.
val nc2 = NetworkCapabilities(agent.nc)
- .removeCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .removeCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
agent.sendNetworkCapabilities(nc2)
callback.expectCapabilitiesThat(agent.network) {
- !it.hasCapability(NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ !it.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
}
}
diff --git a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
index 81a9e30..6833c70 100644
--- a/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
+++ b/tests/cts/net/src/android/net/cts/NetworkWatchlistTest.java
@@ -22,11 +22,13 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeTrue;
+import android.app.UiAutomation;
import android.content.Context;
import android.net.ConnectivityManager;
-import android.platform.test.annotations.AppModeFull;
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -117,42 +119,9 @@
return formatter.toString();
}
- private void saveResourceToFile(String res, String filePath) throws IOException {
- // App can't access /data/local/tmp directly, so we pipe resource to file through stdin.
- ParcelFileDescriptor stdin = pipeFromStdin(filePath);
- pipeResourceToFileDescriptor(res, stdin);
- }
-
- /* Pipe stdin to a file in filePath. Returns PFD for stdin. */
- private ParcelFileDescriptor pipeFromStdin(String filePath) {
- // Not all devices have symlink for /dev/stdin, so use /proc/self/fd/0 directly.
- // /dev/stdin maps to /proc/self/fd/0.
- return runRwCommand("cp /proc/self/fd/0 " + filePath)[1];
- }
-
- private void pipeResourceToFileDescriptor(String res, ParcelFileDescriptor pfd)
- throws IOException {
- InputStream resStream = getClass().getClassLoader().getResourceAsStream(res);
- FileOutputStream fdStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd);
-
- FileUtils.copy(resStream, fdStream);
-
- try {
- fdStream.close();
- } catch (IOException e) {
- }
- }
-
- private static String runCommand(String command) throws IOException {
- return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
- }
-
- private static ParcelFileDescriptor[] runRwCommand(String command) {
- return InstrumentationRegistry.getInstrumentation()
- .getUiAutomation().executeShellCommandRw(command);
- }
-
private void setWatchlistConfig(String watchlistConfigFile) throws Exception {
+ Log.w("NetworkWatchlistTest", "Setting watchlist config " + watchlistConfigFile
+ + " in " + Thread.currentThread().getName());
cleanup();
saveResourceToFile(watchlistConfigFile, TMP_CONFIG_PATH);
final String cmdResult = runCommand(
@@ -160,4 +129,44 @@
assertThat(cmdResult).contains("Success");
cleanup();
}
+
+ private void saveResourceToFile(String res, String filePath) throws IOException {
+ final UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation();
+ // App can't access /data/local/tmp directly, so we pipe resource to file through stdin.
+ // Not all devices have symlink for /dev/stdin, so use /proc/self/fd/0 directly.
+ // /dev/stdin maps to /proc/self/fd/0.
+ final ParcelFileDescriptor[] fileDescriptors = uiAutomation.executeShellCommandRw(
+ "cp /proc/self/fd/0 " + filePath);
+
+ ParcelFileDescriptor stdin = fileDescriptors[1];
+ ParcelFileDescriptor stdout = fileDescriptors[0];
+
+ pipeResourceToFileDescriptor(res, stdin);
+
+ // Wait for the process to close its stdout - which should mean it has completed.
+ consumeFile(stdout);
+ }
+
+ private void consumeFile(ParcelFileDescriptor pfd) throws IOException {
+ try (InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+ for (;;) {
+ if (stream.read() == -1) {
+ return;
+ }
+ }
+ }
+ }
+
+ private void pipeResourceToFileDescriptor(String res, ParcelFileDescriptor pfd)
+ throws IOException {
+ try (InputStream resStream = getClass().getClassLoader().getResourceAsStream(res);
+ FileOutputStream fdStream = new ParcelFileDescriptor.AutoCloseOutputStream(pfd)) {
+ FileUtils.copy(resStream, fdStream);
+ }
+ }
+
+ private static String runCommand(String command) throws IOException {
+ return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+ }
}
diff --git a/tests/cts/net/src/android/net/cts/NsdManagerTest.java b/tests/cts/net/src/android/net/cts/NsdManagerTest.java
new file mode 100644
index 0000000..2bcfdc3
--- /dev/null
+++ b/tests/cts/net/src/android/net/cts/NsdManagerTest.java
@@ -0,0 +1,594 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.content.Context;
+import android.net.nsd.NsdManager;
+import android.net.nsd.NsdServiceInfo;
+import android.platform.test.annotations.AppModeFull;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.util.Arrays;
+import java.util.Random;
+import java.util.List;
+import java.util.ArrayList;
+
+@AppModeFull(reason = "Socket cannot bind in instant app mode")
+public class NsdManagerTest extends AndroidTestCase {
+
+ private static final String TAG = "NsdManagerTest";
+ private static final String SERVICE_TYPE = "_nmt._tcp";
+ private static final int TIMEOUT = 2000;
+
+ private static final boolean DBG = false;
+
+ NsdManager mNsdManager;
+
+ NsdManager.RegistrationListener mRegistrationListener;
+ NsdManager.DiscoveryListener mDiscoveryListener;
+ NsdManager.ResolveListener mResolveListener;
+ private NsdServiceInfo mResolvedService;
+
+ public NsdManagerTest() {
+ initRegistrationListener();
+ initDiscoveryListener();
+ initResolveListener();
+ }
+
+ private void initRegistrationListener() {
+ mRegistrationListener = new NsdManager.RegistrationListener() {
+ @Override
+ public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onRegistrationFailed", errorCode);
+ }
+
+ @Override
+ public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onUnregistrationFailed", errorCode);
+ }
+
+ @Override
+ public void onServiceRegistered(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceRegistered", serviceInfo);
+ }
+
+ @Override
+ public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceUnregistered", serviceInfo);
+ }
+ };
+ }
+
+ private void initDiscoveryListener() {
+ mDiscoveryListener = new NsdManager.DiscoveryListener() {
+ @Override
+ public void onStartDiscoveryFailed(String serviceType, int errorCode) {
+ setEvent("onStartDiscoveryFailed", errorCode);
+ }
+
+ @Override
+ public void onStopDiscoveryFailed(String serviceType, int errorCode) {
+ setEvent("onStopDiscoveryFailed", errorCode);
+ }
+
+ @Override
+ public void onDiscoveryStarted(String serviceType) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceType(serviceType);
+ setEvent("onDiscoveryStarted", info);
+ }
+
+ @Override
+ public void onDiscoveryStopped(String serviceType) {
+ NsdServiceInfo info = new NsdServiceInfo();
+ info.setServiceType(serviceType);
+ setEvent("onDiscoveryStopped", info);
+ }
+
+ @Override
+ public void onServiceFound(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceFound", serviceInfo);
+ }
+
+ @Override
+ public void onServiceLost(NsdServiceInfo serviceInfo) {
+ setEvent("onServiceLost", serviceInfo);
+ }
+ };
+ }
+
+ private void initResolveListener() {
+ mResolveListener = new NsdManager.ResolveListener() {
+ @Override
+ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
+ setEvent("onResolveFailed", errorCode);
+ }
+
+ @Override
+ public void onServiceResolved(NsdServiceInfo serviceInfo) {
+ mResolvedService = serviceInfo;
+ setEvent("onServiceResolved", serviceInfo);
+ }
+ };
+ }
+
+
+
+ private final class EventData {
+ EventData(String callbackName, NsdServiceInfo info) {
+ mCallbackName = callbackName;
+ mSucceeded = true;
+ mErrorCode = 0;
+ mInfo = info;
+ }
+ EventData(String callbackName, int errorCode) {
+ mCallbackName = callbackName;
+ mSucceeded = false;
+ mErrorCode = errorCode;
+ mInfo = null;
+ }
+ private final String mCallbackName;
+ private final boolean mSucceeded;
+ private final int mErrorCode;
+ private final NsdServiceInfo mInfo;
+ }
+
+ private final List<EventData> mEventCache = new ArrayList<EventData>();
+
+ private void setEvent(String callbackName, int errorCode) {
+ if (DBG) Log.d(TAG, callbackName + " failed with " + String.valueOf(errorCode));
+ EventData eventData = new EventData(callbackName, errorCode);
+ synchronized (mEventCache) {
+ mEventCache.add(eventData);
+ mEventCache.notify();
+ }
+ }
+
+ private void setEvent(String callbackName, NsdServiceInfo info) {
+ if (DBG) Log.d(TAG, "Received event " + callbackName + " for " + info.getServiceName());
+ EventData eventData = new EventData(callbackName, info);
+ synchronized (mEventCache) {
+ mEventCache.add(eventData);
+ mEventCache.notify();
+ }
+ }
+
+ void clearEventCache() {
+ synchronized(mEventCache) {
+ mEventCache.clear();
+ }
+ }
+
+ int eventCacheSize() {
+ synchronized(mEventCache) {
+ return mEventCache.size();
+ }
+ }
+
+ private int mWaitId = 0;
+ private EventData waitForCallback(String callbackName) {
+
+ synchronized(mEventCache) {
+
+ mWaitId ++;
+ if (DBG) Log.d(TAG, "Waiting for " + callbackName + ", id=" + String.valueOf(mWaitId));
+
+ try {
+ long startTime = android.os.SystemClock.uptimeMillis();
+ long elapsedTime = 0;
+ int index = 0;
+ while (elapsedTime < TIMEOUT ) {
+ // first check if we've received that event
+ for (; index < mEventCache.size(); index++) {
+ EventData e = mEventCache.get(index);
+ if (e.mCallbackName.equals(callbackName)) {
+ if (DBG) Log.d(TAG, "exiting wait id=" + String.valueOf(mWaitId));
+ return e;
+ }
+ }
+
+ // Not yet received, just wait
+ mEventCache.wait(TIMEOUT - elapsedTime);
+ elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
+ }
+ // we exited the loop because of TIMEOUT; fail the call
+ if (DBG) Log.d(TAG, "timed out waiting id=" + String.valueOf(mWaitId));
+ return null;
+ } catch (InterruptedException e) {
+ return null; // wait timed out!
+ }
+ }
+ }
+
+ private EventData waitForNewEvents() throws InterruptedException {
+ if (DBG) Log.d(TAG, "Waiting for a bit, id=" + String.valueOf(mWaitId));
+
+ long startTime = android.os.SystemClock.uptimeMillis();
+ long elapsedTime = 0;
+ synchronized (mEventCache) {
+ int index = mEventCache.size();
+ while (elapsedTime < TIMEOUT ) {
+ // first check if we've received that event
+ for (; index < mEventCache.size(); index++) {
+ EventData e = mEventCache.get(index);
+ return e;
+ }
+
+ // Not yet received, just wait
+ mEventCache.wait(TIMEOUT - elapsedTime);
+ elapsedTime = android.os.SystemClock.uptimeMillis() - startTime;
+ }
+ }
+
+ return null;
+ }
+
+ private String mServiceName;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ if (DBG) Log.d(TAG, "Setup test ...");
+ mNsdManager = (NsdManager) getContext().getSystemService(Context.NSD_SERVICE);
+
+ Random rand = new Random();
+ mServiceName = new String("NsdTest");
+ for (int i = 0; i < 4; i++) {
+ mServiceName = mServiceName + String.valueOf(rand.nextInt(10));
+ }
+ }
+
+ @Override
+ public void tearDown() throws Exception {
+ if (DBG) Log.d(TAG, "Tear down test ...");
+ super.tearDown();
+ }
+
+ public void testNDSManager() throws Exception {
+ EventData lastEvent = null;
+
+ if (DBG) Log.d(TAG, "Starting test ...");
+
+ NsdServiceInfo si = new NsdServiceInfo();
+ si.setServiceType(SERVICE_TYPE);
+ si.setServiceName(mServiceName);
+
+ byte testByteArray[] = new byte[] {-128, 127, 2, 1, 0, 1, 2};
+ String String256 = "1_________2_________3_________4_________5_________6_________" +
+ "7_________8_________9_________10________11________12________13________" +
+ "14________15________16________17________18________19________20________" +
+ "21________22________23________24________25________123456";
+
+ // Illegal attributes
+ try {
+ si.setAttribute(null, (String) null);
+ fail("Could set null key");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("", (String) null);
+ fail("Could set empty key");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(String256, (String) null);
+ fail("Could set key with 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("key", String256.substring(3));
+ fail("Could set key+value combination with more than 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("key", String256.substring(4));
+ fail("Could set key+value combination with 255 characters");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(new String(new byte[]{0x19}), (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute("=", (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ try {
+ si.setAttribute(new String(new byte[]{0x7F}), (String) null);
+ fail("Could set key with invalid character");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+
+ // Allowed attributes
+ si.setAttribute("booleanAttr", (String) null);
+ si.setAttribute("keyValueAttr", "value");
+ si.setAttribute("keyEqualsAttr", "=");
+ si.setAttribute(" whiteSpaceKeyValueAttr ", " value ");
+ si.setAttribute("binaryDataAttr", testByteArray);
+ si.setAttribute("nullBinaryDataAttr", (byte[]) null);
+ si.setAttribute("emptyBinaryDataAttr", new byte[]{});
+ si.setAttribute("longkey", String256.substring(9));
+
+ ServerSocket socket;
+ int localPort;
+
+ try {
+ socket = new ServerSocket(0);
+ localPort = socket.getLocalPort();
+ si.setPort(localPort);
+ } catch (IOException e) {
+ if (DBG) Log.d(TAG, "Could not open a local socket");
+ assertTrue(false);
+ return;
+ }
+
+ if (DBG) Log.d(TAG, "Port = " + String.valueOf(localPort));
+
+ clearEventCache();
+
+ mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+ lastEvent = waitForCallback("onServiceRegistered"); // id = 1
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(eventCacheSize() == 1);
+
+ // We may not always get the name that we tried to register;
+ // This events tells us the name that was registered.
+ String registeredName = lastEvent.mInfo.getServiceName();
+ si.setServiceName(registeredName);
+
+ clearEventCache();
+
+ mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD,
+ mDiscoveryListener);
+
+ // Expect discovery started
+ lastEvent = waitForCallback("onDiscoveryStarted"); // id = 2
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ // Remove this event, so accounting becomes easier later
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+
+ // Expect a service record to be discovered (and filter the ones
+ // that are unrelated to this test)
+ boolean found = false;
+ for (int i = 0; i < 32; i++) {
+
+ lastEvent = waitForCallback("onServiceFound"); // id = 3
+ if (lastEvent == null) {
+ // no more onServiceFound events are being reported!
+ break;
+ }
+
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
+ // Save it, as it will get overwritten with new serviceFound events
+ si = lastEvent.mInfo;
+ found = true;
+ }
+
+ // Remove this event from the event cache, so it won't be found by subsequent
+ // calls to waitForCallback
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+ }
+
+ assertTrue(found);
+
+ // We've removed all serviceFound events, and we've removed the discoveryStarted
+ // event as well, so now the event cache should be empty!
+ assertTrue(eventCacheSize() == 0);
+
+ // Resolve the service
+ clearEventCache();
+ mNsdManager.resolveService(si, mResolveListener);
+ lastEvent = waitForCallback("onServiceResolved"); // id = 4
+
+ assertNotNull(mResolvedService);
+
+ // Check Txt attributes
+ assertEquals(8, mResolvedService.getAttributes().size());
+ assertTrue(mResolvedService.getAttributes().containsKey("booleanAttr"));
+ assertNull(mResolvedService.getAttributes().get("booleanAttr"));
+ assertEquals("value", new String(mResolvedService.getAttributes().get("keyValueAttr")));
+ assertEquals("=", new String(mResolvedService.getAttributes().get("keyEqualsAttr")));
+ assertEquals(" value ", new String(mResolvedService.getAttributes()
+ .get(" whiteSpaceKeyValueAttr ")));
+ assertEquals(String256.substring(9), new String(mResolvedService.getAttributes()
+ .get("longkey")));
+ assertTrue(Arrays.equals(testByteArray,
+ mResolvedService.getAttributes().get("binaryDataAttr")));
+ assertTrue(mResolvedService.getAttributes().containsKey("nullBinaryDataAttr"));
+ assertNull(mResolvedService.getAttributes().get("nullBinaryDataAttr"));
+ assertTrue(mResolvedService.getAttributes().containsKey("emptyBinaryDataAttr"));
+ assertNull(mResolvedService.getAttributes().get("emptyBinaryDataAttr"));
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": Port = " +
+ String.valueOf(lastEvent.mInfo.getPort()));
+
+ assertTrue(lastEvent.mInfo.getPort() == localPort);
+ assertTrue(eventCacheSize() == 1);
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ // Unregister the service
+ mNsdManager.unregisterService(mRegistrationListener);
+ lastEvent = waitForCallback("onServiceUnregistered"); // id = 5
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ // Expect a callback for service lost
+ lastEvent = waitForCallback("onServiceLost"); // id = 6
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+ // Register service again to see if we discover it
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ si = new NsdServiceInfo();
+ si.setServiceType(SERVICE_TYPE);
+ si.setServiceName(mServiceName);
+ si.setPort(localPort);
+
+ // Create a new registration listener and register same service again
+ initRegistrationListener();
+
+ mNsdManager.registerService(si, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
+
+ lastEvent = waitForCallback("onServiceRegistered"); // id = 7
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ registeredName = lastEvent.mInfo.getServiceName();
+
+ // Expect a record to be discovered
+ // Expect a service record to be discovered (and filter the ones
+ // that are unrelated to this test)
+ found = false;
+ for (int i = 0; i < 32; i++) {
+
+ lastEvent = waitForCallback("onServiceFound"); // id = 8
+ if (lastEvent == null) {
+ // no more onServiceFound events are being reported!
+ break;
+ }
+
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ if (lastEvent.mInfo.getServiceName().equals(registeredName)) {
+ // Save it, as it will get overwritten with new serviceFound events
+ si = lastEvent.mInfo;
+ found = true;
+ }
+
+ // Remove this event from the event cache, so it won't be found by subsequent
+ // calls to waitForCallback
+ synchronized (mEventCache) {
+ mEventCache.remove(lastEvent);
+ }
+ }
+
+ assertTrue(found);
+
+ // Resolve the service
+ clearEventCache();
+ mNsdManager.resolveService(si, mResolveListener);
+ lastEvent = waitForCallback("onServiceResolved"); // id = 9
+
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+
+ if (DBG) Log.d(TAG, "id = " + String.valueOf(mWaitId) + ": ServiceName = " +
+ lastEvent.mInfo.getServiceName());
+
+ assertTrue(lastEvent.mInfo.getServiceName().equals(registeredName));
+
+ assertNotNull(mResolvedService);
+
+ // Check that we don't have any TXT records
+ assertEquals(0, mResolvedService.getAttributes().size());
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ mNsdManager.stopServiceDiscovery(mDiscoveryListener);
+ lastEvent = waitForCallback("onDiscoveryStopped"); // id = 10
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(checkCacheSize(1));
+
+ checkForAdditionalEvents();
+ clearEventCache();
+
+ mNsdManager.unregisterService(mRegistrationListener);
+
+ lastEvent = waitForCallback("onServiceUnregistered"); // id = 11
+ assertTrue(lastEvent != null);
+ assertTrue(lastEvent.mSucceeded);
+ assertTrue(checkCacheSize(1));
+ }
+
+ boolean checkCacheSize(int size) {
+ synchronized (mEventCache) {
+ int cacheSize = mEventCache.size();
+ if (cacheSize != size) {
+ Log.d(TAG, "id = " + mWaitId + ": event cache size = " + cacheSize);
+ for (int i = 0; i < cacheSize; i++) {
+ EventData e = mEventCache.get(i);
+ String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
+ Log.d(TAG, "eventName is " + e.mCallbackName + sname);
+ }
+ }
+ return (cacheSize == size);
+ }
+ }
+
+ boolean checkForAdditionalEvents() {
+ try {
+ EventData e = waitForNewEvents();
+ if (e != null) {
+ String sname = (e.mInfo != null) ? "(" + e.mInfo.getServiceName() + ")" : "";
+ Log.d(TAG, "ignoring unexpected event " + e.mCallbackName + sname);
+ }
+ return (e == null);
+ }
+ catch (InterruptedException ex) {
+ return false;
+ }
+ }
+}
+
diff --git a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
index 37bdd44..1d9268a 100755
--- a/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/cts/net/src/android/net/cts/TrafficStatsTest.java
@@ -59,9 +59,11 @@
assertTrue(TrafficStats.getTotalRxBytes() >= 0);
}
- public void testValidPacketStats() {
+ public void testValidIfaceStats() {
assertTrue(TrafficStats.getTxPackets("lo") >= 0);
assertTrue(TrafficStats.getRxPackets("lo") >= 0);
+ assertTrue(TrafficStats.getTxBytes("lo") >= 0);
+ assertTrue(TrafficStats.getRxBytes("lo") >= 0);
}
public void testThreadStatsTag() throws Exception {
@@ -109,6 +111,8 @@
final long uidRxPacketsBefore = TrafficStats.getUidRxPackets(Process.myUid());
final long ifaceTxPacketsBefore = TrafficStats.getTxPackets("lo");
final long ifaceRxPacketsBefore = TrafficStats.getRxPackets("lo");
+ final long ifaceTxBytesBefore = TrafficStats.getTxBytes("lo");
+ final long ifaceRxBytesBefore = TrafficStats.getRxBytes("lo");
// Transfer 1MB of data across an explicitly localhost socket.
final int byteCount = 1024;
@@ -189,8 +193,12 @@
final long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
final long ifaceTxPacketsAfter = TrafficStats.getTxPackets("lo");
final long ifaceRxPacketsAfter = TrafficStats.getRxPackets("lo");
+ final long ifaceTxBytesAfter = TrafficStats.getTxBytes("lo");
+ final long ifaceRxBytesAfter = TrafficStats.getRxBytes("lo");
final long ifaceTxDeltaPackets = ifaceTxPacketsAfter - ifaceTxPacketsBefore;
final long ifaceRxDeltaPackets = ifaceRxPacketsAfter - ifaceRxPacketsBefore;
+ final long ifaceTxDeltaBytes = ifaceTxBytesAfter - ifaceTxBytesBefore;
+ final long ifaceRxDeltaBytes = ifaceRxBytesAfter - ifaceRxBytesBefore;
// Localhost traffic *does* count against per-UID stats.
/*
@@ -246,9 +254,13 @@
assertInRange("uidrxb", uidRxDeltaBytes, pktBytes + minExpExtraPktBytes,
pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes + deltaRxOtherPktBytes);
assertInRange("iftxp", ifaceTxDeltaPackets, packetCount + minExpectedExtraPackets,
- packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
+ packetCount + packetCount + maxExpectedExtraPackets);
assertInRange("ifrxp", ifaceRxDeltaPackets, packetCount + minExpectedExtraPackets,
- packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
+ packetCount + packetCount + maxExpectedExtraPackets);
+ assertInRange("iftxb", ifaceTxDeltaBytes, pktBytes + minExpExtraPktBytes,
+ pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes);
+ assertInRange("ifrxb", ifaceRxDeltaBytes, pktBytes + minExpExtraPktBytes,
+ pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes);
// Localhost traffic *does* count against total stats.
// Check the total stats increased after test data transfer over localhost has been made.
@@ -264,6 +276,10 @@
totalTxPacketsAfter >= totalTxPacketsBefore + ifaceTxDeltaPackets);
assertTrue("ifrxp: " + ifaceRxPacketsBefore + " -> " + ifaceRxPacketsAfter,
totalRxPacketsAfter >= totalRxPacketsBefore + ifaceRxDeltaPackets);
+ assertTrue("iftxb: " + ifaceTxBytesBefore + " -> " + ifaceTxBytesAfter,
+ totalTxBytesAfter >= totalTxBytesBefore + ifaceTxDeltaBytes);
+ assertTrue("ifrxb: " + ifaceRxBytesBefore + " -> " + ifaceRxBytesAfter,
+ totalRxBytesAfter >= totalRxBytesBefore + ifaceRxDeltaBytes);
// Localhost traffic should *not* count against mobile stats,
// There might be some other traffic, but nowhere near 1MB.
diff --git a/tests/cts/net/src/android/net/cts/TunUtils.java b/tests/cts/net/src/android/net/cts/TunUtils.java
index adaba9d..7887385 100644
--- a/tests/cts/net/src/android/net/cts/TunUtils.java
+++ b/tests/cts/net/src/android/net/cts/TunUtils.java
@@ -47,7 +47,7 @@
protected static final int IP6_PROTO_OFFSET = 6;
private static final int DATA_BUFFER_LEN = 4096;
- private static final int TIMEOUT = 1000;
+ private static final int TIMEOUT = 2000;
private final List<byte[]> mPackets = new ArrayList<>();
private final ParcelFileDescriptor mTunFd;
diff --git a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
index b18c1e7..c95dc28 100644
--- a/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
+++ b/tests/cts/net/util/java/android/net/cts/util/CtsTetheringUtils.java
@@ -28,7 +28,11 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import android.content.BroadcastReceiver;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.net.Network;
import android.net.TetheredClient;
import android.net.TetheringManager;
@@ -42,6 +46,7 @@
import androidx.annotation.NonNull;
+import com.android.compatibility.common.util.SystemUtil;
import com.android.net.module.util.ArrayTrackRecord;
import java.util.Collection;
@@ -300,6 +305,26 @@
}));
}
+ public void assumeWifiTetheringSupported(final Context ctx) throws Exception {
+ assumeTetheringSupported();
+
+ assumeTrue(!getTetheringInterfaceRegexps().getTetherableWifiRegexs().isEmpty());
+
+ final PackageManager pm = ctx.getPackageManager();
+ assumeTrue(pm.hasSystemFeature(PackageManager.FEATURE_WIFI));
+
+ WifiManager wm = ctx.getSystemService(WifiManager.class);
+ // Wifi feature flags only work when wifi is on.
+ final boolean previousWifiEnabledState = wm.isWifiEnabled();
+ try {
+ if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi enable");
+ waitForWifiEnabled(ctx);
+ assumeTrue(wm.isPortableHotspotSupported());
+ } finally {
+ if (!previousWifiEnabledState) SystemUtil.runShellCommand("svc wifi disable");
+ }
+ }
+
public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
return mTetherableRegex;
}
@@ -313,6 +338,31 @@
}
}
+ private static void waitForWifiEnabled(final Context ctx) throws Exception {
+ WifiManager wm = ctx.getSystemService(WifiManager.class);
+ if (wm.isWifiEnabled()) return;
+
+ final ConditionVariable mWaiting = new ConditionVariable();
+ final BroadcastReceiver receiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
+ if (wm.isWifiEnabled()) mWaiting.open();
+ }
+ }
+ };
+ try {
+ ctx.registerReceiver(receiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
+ if (!mWaiting.block(DEFAULT_TIMEOUT_MS)) {
+ assertTrue("Wifi did not become enabled after " + DEFAULT_TIMEOUT_MS + "ms",
+ wm.isWifiEnabled());
+ }
+ } finally {
+ ctx.unregisterReceiver(receiver);
+ }
+ }
+
public TestTetheringEventCallback registerTetheringEventCallback() {
final TestTetheringEventCallback tetherEventCallback =
new TestTetheringEventCallback();
diff --git a/tests/cts/tethering/Android.bp b/tests/cts/tethering/Android.bp
index 85bb0e0..b1d4a60 100644
--- a/tests/cts/tethering/Android.bp
+++ b/tests/cts/tethering/Android.bp
@@ -17,7 +17,7 @@
defaults: ["cts_defaults"],
libs: [
- "android.test.base.stubs",
+ "android.test.base",
],
srcs: [
diff --git a/tests/cts/tethering/AndroidManifest.xml b/tests/cts/tethering/AndroidManifest.xml
index 665002e..911dbf2 100644
--- a/tests/cts/tethering/AndroidManifest.xml
+++ b/tests/cts/tethering/AndroidManifest.xml
@@ -19,6 +19,7 @@
package="android.tethering.cts">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 87787b9..71a81ff 100644
--- a/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/cts/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -204,14 +204,15 @@
@Test
public void testStartTetheringWithStateChangeBroadcast() throws Exception {
- if (!mTM.isTetheringSupported()) return;
+ final TestTetheringEventCallback tetherEventCallback =
+ mCtsTetheringUtils.registerTetheringEventCallback();
+ try {
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ } finally {
+ mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
+ }
final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
- if (wifiRegexs.length == 0) return;
-
- final String[] tetheredIfaces = mTM.getTetheredIfaces();
- assertTrue(tetheredIfaces.length == 0);
-
final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
final TetheringRequest request = new TetheringRequest.Builder(TETHERING_WIFI)
.setShouldShowEntitlementUi(false).build();
@@ -252,33 +253,31 @@
public void testRegisterTetheringEventCallback() throws Exception {
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
- tetherEventCallback.assumeTetheringSupported();
-
- if (!isWifiTetheringSupported(tetherEventCallback)) {
- mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
- return;
- }
-
- mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
-
- final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
- assertEquals(1, tetheredIfaces.size());
- final String wifiTetheringIface = tetheredIfaces.get(0);
-
- mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
try {
- final int ret = mTM.tether(wifiTetheringIface);
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
- // There is no guarantee that the wifi interface will be available after disabling
- // the hotspot, so don't fail the test if the call to tether() fails.
- assumeTrue(ret == TETHER_ERROR_NO_ERROR);
+ mCtsTetheringUtils.startWifiTethering(tetherEventCallback);
- // If calling #tether successful, there is a callback to tell the result of tethering
- // setup.
- tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+ final List<String> tetheredIfaces = tetherEventCallback.getTetheredInterfaces();
+ assertEquals(1, tetheredIfaces.size());
+ final String wifiTetheringIface = tetheredIfaces.get(0);
+
+ mCtsTetheringUtils.stopWifiTethering(tetherEventCallback);
+
+ try {
+ final int ret = mTM.tether(wifiTetheringIface);
+ // There is no guarantee that the wifi interface will be available after disabling
+ // the hotspot, so don't fail the test if the call to tether() fails.
+ if (ret == TETHER_ERROR_NO_ERROR) {
+ // If calling #tether successful, there is a callback to tell the result of
+ // tethering setup.
+ tetherEventCallback.expectErrorOrTethered(wifiTetheringIface);
+ }
+ } finally {
+ mTM.untether(wifiTetheringIface);
+ }
} finally {
- mTM.untether(wifiTetheringIface);
mCtsTetheringUtils.unregisterTetheringEventCallback(tetherEventCallback);
}
}
@@ -311,10 +310,8 @@
public void testStopAllTethering() throws Exception {
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
- tetherEventCallback.assumeTetheringSupported();
-
try {
- if (!isWifiTetheringSupported(tetherEventCallback)) return;
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
// TODO: start ethernet tethering here when TetheringManagerTest is moved to
// TetheringIntegrationTest.
@@ -415,14 +412,15 @@
assumeTrue(mPm.hasSystemFeature(FEATURE_TELEPHONY));
final TestTetheringEventCallback tetherEventCallback =
mCtsTetheringUtils.registerTetheringEventCallback();
- tetherEventCallback.assumeTetheringSupported();
- final boolean previousWifiEnabledState = mWm.isWifiEnabled();
+
+ boolean previousWifiEnabledState = false;
try {
- if (!isWifiTetheringSupported(tetherEventCallback)) return;
+ tetherEventCallback.assumeWifiTetheringSupported(mContext);
+ previousWifiEnabledState = mWm.isWifiEnabled();
if (previousWifiEnabledState) {
- mCtsNetUtils.disconnectFromWifi(null);
+ mCtsNetUtils.ensureWifiDisconnected(null);
}
final TestNetworkCallback networkCallback = new TestNetworkCallback();